デリゲートとはタイプセーフティ (型の安全性) を保証したコールバック関数で、C++の関数ポインタに代わるものです。
delegate 戻り値の型 デリゲート名( 引数, ... );
これは、C++で次のように記述することと同じです。
typedef 戻り値の型 (*関数ポインタ名)( 引数, ... );
基本的な形式のデリゲートがあらかじめ定義されているため、それを使えるならばあらためて定義する必要はありません。
戻り値 | 引数 | 名前空間 | |
---|---|---|---|
MethodInvoker | なし | なし | System.Windows.Forms |
Action | なし | なし | System |
Action<T> | なし | T | System |
Predicate<T> | bool | T | System |
Func<TResult> | TResult | なし | System |
Func<T, TResult> | TResult | T | System |
public delegate void MethodInvoker()MethodInvoker 代理人 (System.Windows.Forms) | Microsoft Learn
戻り値なし、引数なしのデリゲートです。これは.NET Framework 1.1から実装されていますが、.NET Framework 3.5以降の対応で良ければActionで代用可能です。
public delegate void Action()Action 代理人 (System) | Microsoft Learn
Action action = delegate() { };
Actionは引数がないため、かっこを省略できます。
Action action = delegate { };
またラムダ式では、次のようにも記述できます。
Action action = () => { };
public delegate void Action<in T>(T obj)Action<T> 代理人 (System) | Microsoft Learn
objは、このデリゲートでカプセル化するメソッドのパラメータです。そしてそのパラメータは反変であり、指定の型またはそれより派生の少ない型を使用できます。
このAction<T>と同類のデリゲートとして、Action<T1,T2>からAction<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16>までの、16種類があります。
public delegate bool Predicate<in T>(T obj)Predicate<T> 代理人 (System) | Microsoft Learn
public delegate TResult Func<out TResult>()Func<TResult> 代理人 (System) | Microsoft Learn
Func<int> func = () =>
{
return 10;
};
int r = func(); // 10
public delegate TResult Func<in T, out TResult>(T arg)Func<T,TResult> 代理人 (System) | Microsoft Learn
Func<int, bool> func = (num) => { return num < 10; }; bool r1 = func(5); // true bool r2 = func(10); // false
これと同類のデリゲートとして、16個の引数を取るFunc<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16,TResult>までの16種類があります。
ここでは、次のメソッドをデリゲートから呼び出す方法を考えます。
public static int Callback(int x) { return x * 2; }
まずはこのメソッドの戻り値と引数の型が一致するように、delegateキーワードを用いてデリゲートを宣言します。
delegate int MyDelegate(int x);
この例ではデリゲート名がMyDelegate
として宣言されています。こうして宣言されたデリゲートは、
MyDelegate handler = new MyDelegate(Callback);
とすることでインスタンスを作成でき、
int a = handler(10);
とすることで、それに結びつけられたメソッドを呼び出せます。デリゲートの使用 (C# プログラミング ガイド) | MSDN
C# 2.0以降ではnewを省略しメソッド名を指定するだけで、デリゲートをインスタンス化できます。方法 : デリゲートを宣言し、インスタンス化して使用する (C# プログラミング ガイド) | Microsoft Learn
記述 | |
---|---|
C# 2.0より前 | MyDelegate handler = new MyDelegate(Callback); |
C# 2.0以降 | MyDelegate handler = Callback; |
1つのデリゲートに複数のメソッドを割り当て、それらを一括して呼び出せます。デリゲートを結合する方法 (マルチキャスト デリゲート) - C# | Microsoft Learn
MyDelegate handler = new MyDelegate(Method1) + new MyDelegate(Method2)
handler += new MyDelegate(Method3);
handler(); // Method1とMethod2とMethod3が呼ばれる
ラムダ式は+演算子で結合できないため、次のように分けて割り当てます。
Action handler = () => { Console.Write("1"); };
handler += () => { Console.Write("2"); };
handler += () => { Console.Write("3"); };
handler(); // "123" と出力される
デリゲートから呼び出されるメソッドは、GetInvocationList()で確認できます。
public virtual Delegate[] GetInvocationList ();Delegate.GetInvocationList メソッド (System) | Microsoft Learn
void A() { } void B() { } { Action handler = null; handler += A; handler += B; Delegate[] lists = handler.GetInvocationList(); string name0 = lists[0].Method.Name; // "A" string name1 = lists[1].Method.Name; // "B" handler -= A; int length1 = handler.GetInvocationList().Length; // 1 (割り当てが1つ解除されている) handler -= A; // すでに解除しているメソッドの解除を試みる int length2 = handler.GetInvocationList().Length; // 1 (変化なし) handler -= B; bool r = handler == null; // true (すべての割り当てが解除された) }
C# 2.0以降ではデリゲートから呼び出すメソッドをインラインで記述することで、メソッドの定義を省略できます。
C# 3.0以降では、メソッドの引数を使用するならばラムダ式を用います。この匿名メソッドが必要となるのは、引数を省略する場合のみです。
たとえばデリゲートが、
delegate int MyDelegate(int x, int y);
のように定義されているとき、通常は
public int Callback(int x, int y) { return x + y; }
のようにコールバック メソッドを定義した上で、それを
MyDelegate handler = new MyDelegate(DelegateMethod); int a = handler(2, 3);
のようにデリゲートをインスタンス化して利用します。一方で匿名メソッドではコールバック メソッドの定義を省略し、
MyDelegate handler = delegate (int x, int y) { return x + y; }; int a = handler(2,3);
のように記述できます。これをイベントに適用すると、次のようにコールバックメソッドを省略できます。
button.Click += delegate( object sender, EventArgs e )
{
// このメソッドには名前がない。匿名メソッド
};
このとき引数が不要ならば、かっこごと省略できます。
button.Click += delegate { };
ラムダ式はC# 3.0以降でサポートされます。
たとえば次のようにデリゲートが宣言されていると、
delegate int MyDelegate(int x);
従来はdelegateキーワードを用いて
MyDelegate handler1 = delegate (int x) { return x * 2; };
のようにしていた記述を、ラムダ演算子 (lambda operator) を用いることで、
MyDelegate handler2 = (x) => { return x * 2; };
のように引数の型を省略できます。さらに式形式のラムダでは、波かっことreturnも省略できます。
MyDelegate handler3 = (x) => x * 2;
delegateキーワードを用いた形式では、引数が不要ならばかっこごと省略できますが、ラムダ式では省略できません。
引数の型を省略するならば、すべて省略します。一部だけ省略すると「ラムダ パラメーターの使用方法に一貫性がありません。パラメーター型はすべて明示的であるか、またはすべて暗黙的である必要があります」としてCS0748でエラーとなります。
下表のように2つの形式があります。
形式 | 構文 |
---|---|
ステートメント形式のラムダ (Statement Lambdas) / 文形式 | (parameters) => {expression;} |
式形式のラムダ (Expression Lambdas) | (parameters) => expression |
また、引数の数によっても形式が異なります。
パラメータの数 | 例 |
---|---|
なし | () => {} |
1つ | (x) => {} |
x => {}かっこを省略できる |
|
2つ以上 | (x, y) => {} |
匿名としたメソッドはコンパイラによって名前が与えられ、デバッグ時には"DisplayClass"のような名前で確認できます。.net - The significance of <> in C# - Stack Overflow