C#のdelegateの機能はeventの機能によく似ており、実際に、event機能はdelegate機能を利用して作られています。しかし、delegateとeventは、想定される使われ方が違っており、機能面でも相違がありまる。C#のdelegateとeventについて以降に説明します。

  • 関数ポインターの拡張
  • コールバック というイベント駆動
  • EventHandlerとは

関数ポインターの拡張

C# の delegate は、 C, C++ の関数ポインター変数のようなものですが、 次のような点が拡張されています。

  • 通常の関数だけでなく、クラスのメソッドも格納(アタッチ)できる
  • 複数の関数を格納可能で、呼び出し時には格納した順に逐次実行される
  • 関数の着脱(アタッチ、デタッチ)が自由にできる
// Delegate の型を宣言 
delegate void DemoDelegate(string name);

class Foo
{
    public void Hello(string name)
    {
        Console.WriteLine("(method) : Check {0}!", name);
    }
} 

class Program
{
    // static な関数 
    static void StaticHello(string name)
    {
        Console.WriteLine("(static) : Check {0}!", name);
    }
    static void Main(string[] args)
    {
        Foo foo = new Foo();

        // delegate の変数 
        // 初期化した後に実際に呼び出したい関数を格納します。 
        // 呼び出し時には何もしない関数 delegate(string name){} も実行されます。
        DemoDelegate funcs = delegate(string name) { };

        funcs += foo.Hello;    // メソッドの格納 
        funcs += StaticHello;  // static 関数の格納 
        funcs += (string name) => { // 無名関数の格納(=> を使うラムダ式)
            Console.WriteLine("(lambda) : Check {0}!", name);
        };

        // 格納した関数の呼び出し 
        funcs("CallBack");
    }
}

実行した結果を次に示します。

(method) : Check CallBack!
(static) : Check CallBack!
(lambda) : Check CallBack!

コールバック というイベント駆動

C# の event は、一般的な “イベント-コールバック” を 、C# の delegate の機能を使って実現しています。C# の event には 、delegate と同じのようにメソッドを登録することができます。ただし、以下の点が違います。

  • ローカルな変数には使えず、必ずクラスのメンバーを使用する
  • 同じクラスのメソッドのみ呼び出し可能である

event は、 delegate の変数のように関数を登録できますが、 直接呼び出すことはできません。 この制限により、”イベントに関連付けたコールバック(関数)はイベントが発生した時に呼び出される” という形式になります。これは重要なことで、 上記の DemoDelegate デリゲートは、「DemoDelegate funcs」さえ定義すれば、どこでも直接、呼び出すことが可能です。 しかし本来クリックイベントは、本当のクリックが発生したときにのみ発生して欲しいものです。このために、event キーワードを付けてイベントを定義します。

class Button
{
    // イベント 
    // (クラス定義内でしか event は使えない) 
    public event DemoDelegate DemoEvent = delegate(string name) {};
    // イベントの発生 
    public void Clicked(string name)
    {
        Console.WriteLine("<Button Click>");
        // 登録した関数を呼び出す 
        // (直接には Button クラス内でしか実行できない) 
        DemoEvent(name);
    }
}
class Program
{
    // static な関数 
    static void StaticHello(string name)
    {
        Console.WriteLine("(static) : Check {0}!", name);
    }
    static void Main(string[] args)
    {
        Button btn = new Button();

        // 関数の登録(アタッチ) 
        btn.DemoEvent += foo.Hello;
        btn.DemoEvent += StaticHello;
        btn.DemoEvent += (string name) =>
        {
            Console.WriteLine("(lambda) :  Check {0}!", name);
        };

        // イベントの発生 
        btn.Clicked("Event"); 

    }
}

実行した結果を次に示します。

<Button Click>
(method) : Check Event!
(static) : Check Event!
(lambda) :  Check Event!

EventHandlerとは

C#では、イベントハンドラーという呼ばれ方をしていますが、要はデリゲートなのです。
Windowsフォームアプリケーションを作成して、ボタンのイベントハンドラを自動生成すると、次のようなイベントハンドラの定義のコードが自動生成されます。このEventHandlerとはなにか、ボタンを押すと、指定された処理がどのようにして実行されるのかを調べてみました。

public delegate void EventHandler(object sender, EventArgs e);

読み解くと、EventHandlerは、void型で、引数にobject型とEventArgs型を持つdelegateとあります。つまり、ボタンは、Clickというデリゲート型の変数を持ち、その変数は、EventHandlerというデリゲート型として定義されています。このコードにより、ボタンがクリックされると、イベントハンドラという名前のデリゲートが実行されます。デリゲートは、関数を覚えられる変数なので、あらかじめ代入されていた関数が実行されます。