C#におけるメソッド型の抽象化:delegate、Action、Func、Predicateの活用

メソッドを値として扱えるようにするための型安全な仕組みがC#の「デリゲート」です。これは単なる関数ポインタの代替ではなく、コンパイル時型チェックを伴う第一級の言語機能であり、コールバック、イベント処理、LINQ演算子など、多くの高水準パターンの基盤となります。

1. 主要なデリゲート型の特徴と用途

・カスタムデリゲート(delegate キーワード)

明示的にシグネチャを定義するタイプで、最大32個のパラメータと任意の戻り値型をサポートします。以下は2つの整数を受け取り、その和を返すデリゲートの例です:

public delegate long SummationDelegate(long a, long b);

static long ComputeSum(long x, long y) => x + y;

// 使用例
SummationDelegate calc = ComputeSum;
long result = calc(42L, 58L); // 100

・Action<T...>

戻り値を持たない汎用デリゲート。0〜16個の型パラメータを指定可能で、コールバックや副作用のある処理に適しています。

  • Action:引数なし
  • Action<string>:1つString引数
  • Action<int, bool, DateTime>:3つの異なる型の引数

実装例:

static void ProcessItems<T>(IEnumerable<T> items, Action<T> handler)
{
    foreach (var item in items)
        handler(item);
}

// 呼び出し
ProcessItems(new[] { "alpha", "beta" }, s => Console.WriteLine($"→ {s}"));

・Func<T..., TResult>

戻り値を持つ汎用デリゲート。最後の型パラメータが戻り値型となり、0〜16個の入力パラメータに対応します。LINQのSelectWhereなど、変換・評価系の操作で広く使用されます。

static TResult Transform<TInput, TResult>(TInput input, Func<TInput, TResult> mapper)
    => mapper(input);

// 使用例
string text = Transform(123, n => $"Number: {n}"); // "Number: 123"

・Predicate<T>

単一の型パラメータを受け取り、boolを返す特殊化されたデリゲート。主に検索・フィルタリング用途(例:Array.Find, List<T>.Find)で利用されます。

record TemperatureRecord(string Location, double Celsius);

var records = new[]
{
    new TemperatureRecord("Tokyo", 24.7),
    new TemperatureRecord("Sapporo", -3.2),
    new TemperatureRecord("Okinawa", 29.1)
};

Predicate<TemperatureRecord> isHot = r => r.Celsius > 25.0;
var hotSpot = Array.Find(records, isHot); // Okinawa

2. 実践的な使い方

・複数ハンドラの登録と実行(マルチキャスト)

デリゲートは「+=」で複数のメソッドを連結でき、呼び出し時に全ハンドラが順次実行されます:

public delegate void StatusUpdateHandler(string message);

StatusUpdateHandler statusLog = null;
statusLog += msg => Console.ForegroundColor = ConsoleColor.Green;
statusLog += msg => Console.WriteLine($"[INFO] {msg}");
statusLog += msg => Console.ResetColor();

statusLog("Initialization complete.");

・ラムダ式による即時定義

短い処理にはラムダ式が簡潔で効果的です:

var numbers = new List<int> { 1, 2, 3, 4, 5 };
var evens = numbers.FindAll(x => x % 2 == 0); // [2, 4]

3. デリゲートの管理とクリア

マルチキャストデリゲートから特定のハンドラを削除するには、-=演算子を使います。すべてのハンドラをクリアするには、null代入が最も安全です:

public StatusUpdateHandler OnStatusChanged;

// 登録
OnStatusChanged += LogToConsole;
OnStatusChanged += SendNotification;

// 全て解除(推奨)
OnStatusChanged = null;

// または個別解除
OnStatusChanged -= LogToConsole;

4. 補足:型安全性と柔軟性

C#のデリゲートは、厳密なシグネチャ一致を要求する一方で、共変性・反変性をサポートしており、継承関係にある型間での代入が可能です(例:Func<object>Func<string>)。また、asyncメソッドとの統合も自然に行えます(Func<Task>Func<int, Task<string>>など)。

タグ: csharp delegate Func Action predicate

7月3日 17:53 投稿