デリゲート

概要

デリゲートはメソッドなどの処理自体を持つことができる型です。
デリゲート型のインスタンスに処理を登録し、そのインスタンスを経由することでどこからでも処理を呼び出すことができます。
Action・Func・EventHandlerなどのデリゲート型が用意されており、そちらを使用することが多いです。

デリゲートの定義

delegateキーワードを指定し、メソッドの要領で定義します。

delegate void HogeDelegate();

引数・戻り値を持つ場合、以下のようにします。

delegate string HogeDelegate(string param);

デリゲートの初期化

newする際に、デリゲートと同一シグネチャのメソッド名または、ラムダ式を指定します。
また、「+」「-」「+=」「-=」演算子で結合・解除 が可能です。

// メソッド指定で初期化
var hogeDelegate1 = new HogeDelegate(HogeMethod);

// ラムダ式指定で初期化
var hogeDelegate2 = new HogeDelegate(() => {});

// 結合
var hogeDelegate3 = hogeDelegate1 + hogeDelegate2;

// 結合解除
var hogeDelegate4 = hogeDelegate3 - hogeDelegate2;

// 結合
hogeDelegate4 += hogeDelegate2;

// 結合解除
hogeDelegate4 -= hogeDelegate2;

デリゲートの実行

例として以下のようなデリゲートを用意します。

delegate void HogeDelegate(string param);

上記デリゲートを以下のように初期化します。

var hogeDelegate = new HogeDelegate(
    param =>
    {
        Thread.Sleep(1000);
        Console.WriteLine(param);
    });
同期実行

Invoke()で登録された処理が同期実行されます。 実行すると、1000ミリ秒待機の後、コンソール出力されます。

hogeDelegate.Invoke("Delegate Execute");

// これでもOK
hogeDelegate("Delegate Execute");
非同期実行

BeginInvoke()で登録された処理が非同期実行されます。 1000ミリ秒待機せずに、後続処理に制御が移ります。 非同期で1000ミリ秒待機後、コンソール出力されます。

hogeDelegate.BeginInvoke("Delegate Execute", new AsyncCallback(ar => Console.WriteLine(ar.AsyncState), "Async Executed");

BeginInvoke()の戻り値を使用することで、実行を同期的に待機することができます。 以下の場合、後続処理がある場合でも1000ミリ秒待機します。

var result = hogeDelegate.BeginInvoke("Delegate Execute", new AsyncCallback(ar => Console.WriteLine(ar.AsyncState), "Async Executed");
hogeDelegate.EndInvoke(result);
動的実行

デリゲートの型がわからない場合、DynamicInvoke()で動的実行することができます。 パラメーターはobject型の可変長引数となります。

hogeDelegate.DynamicInvoke("Delegate Execute");

指定したパラメーター型がデリゲートのパラメーター型と一致しない場合、MemberAccessExceptionをスローします。

// intを渡すと例外
hogeDelegate.DynamicInvoke(0);

デリゲートに複数の処理が登録されている場合、登録された順に順次処理されます。
戻り値がある場合、最後に登録された処理の戻り値が返されます。

例外処理

デリゲートに登録された処理が例外をスローする場合、呼び出し元でcatchできます。
結合されている場合、例外発生時点で処理は打ち切られます。

var del1 = new HogeDelegate(() => throw new InvalidOperationException("Exception 1"));
// 上記が処理されて打ち切られるので、これは処理されない
del1 += () => throw new InvalidOperationException("Exception 2");
try
{
    del1.Invoke();
}
catch (InvalidOperationException e)
{
    Console.WriteLine(e.Message);
}