デリゲート (delegate) / 委譲

デリゲートとはタイプセーフティ (型の安全性) を保証したコールバック関数で、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

MethodInvoker

public delegate void MethodInvoker()
MethodInvoker 代理人 (System.Windows.Forms) | Microsoft Learn

戻り値なし、引数なしのデリゲートです。これは.NET Framework 1.1から実装されていますが、.NET Framework 3.5以降の対応で良ければActionで代用可能です。

Action

public delegate void Action()
Action 代理人 (System) | Microsoft Learn
Action action = delegate()
{
};

Actionは引数がないため、かっこを省略できます。

Action action = delegate
{
};

またラムダ式では、次のようにも記述できます。

Action action = () =>
{
};

Action<T>

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種類があります。

Predicate<T>

public delegate bool Predicate<in T>(T obj)
Predicate<T> 代理人 (System) | Microsoft Learn

Func<TResult>

public delegate TResult Func<out TResult>()
Func<TResult> 代理人 (System) | Microsoft Learn
Func<int> func = () =>
{
    return 10;
};

int r = func(); // 10
Func<T, TResult>
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

newの省略

C# 2.0以降ではnewを省略しメソッド名を指定するだけで、デリゲートをインスタンス化できます。方法 : デリゲートを宣言し、インスタンス化して使用する (C# プログラミング ガイド) | Microsoft Learn

  記述
C# 2.0より前
MyDelegate handler = new MyDelegate(Callback);
C# 2.0以降
MyDelegate handler =                Callback;

マルチキャスト デリゲート (multicast delegate)

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 (すべての割り当てが解除された)
}

匿名関数 (Anonymous Functions)

匿名メソッド (Anonymous Methods)

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
{
};

ラムダ式 (lambda expression)

ラムダ式は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

匿名としたメソッドはコンパイラによって名前が与えられ、デバッグ時には"DisplayClass"のような名前で確認できます。.net - The significance of <> in C# - Stack Overflow

Microsoft Learnから検索