メソッドを値として扱えるようにするための型安全な仕組みが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のSelectやWhereなど、変換・評価系の操作で広く使用されます。
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>>など)。