イベント (event)

イベントはデリゲートに基づくため、そちらも理解する必要があります。

イベントに関わるクラス

  • イベントを送信 (発生) するクラス … パブリッシャー (publisher)
  • イベントを受信 (処理) するクラス … サブスクライバー (subscriber)

EventHandler デリゲート

[System.Runtime.InteropServices.ComVisible(true)]
[System.Serializable]
public delegate void EventHandler(object sender, EventArgs e);
EventHandler Delegate (System) | Microsoft Docs

イベントの登録

イベントに加算代入演算子でハンドラのオブジェクトを代入することで、イベントにサブスクライバーを登録できます。

button.Click += new EventHandler( button_Click );

このときコールバックメソッドは、

void button_Click( object sender, EventArgs e )
{
    ...
}

のように定義します。ところでC# 2.0以降ならば、new EventHandler()を省略して次のようにも記述できます。

button.Click += button_Click;

複数のオブジェクトへの登録

同一のイベントハンドラをほかにも使用するならば、

EventHandler clickEventHandler = new EventHandler( button_Click );

button1.Click += clickEventHandler;
button2.Click += clickEventHandler;

のようにも記述できます。

コールバックメソッドの省略

匿名メソッドを使用することで、コールバックメソッドを省略できます。

button.Click += delegate( object sender, EventArgs e ) { ... };

引数を使用しないならば、それを省略することもできます。

button.Click += delegate { ... };

C# 3.0以降ならばラムダ式を用いることで、delegateキーワードを省略できます。ただしこの方法では型名を省略できますが、引数名は省略できません。

button.Click += (sender, e) => { ... };

イベントの解除

登録時とは逆で、ハンドラのオブジェクトを減算します。

button.Click -= new EventHandler( button_Click );

匿名メソッドではメソッドの名前が存在しないため、生成したハンドラ オブジェクトを保持しておき、それを用いて解除します。

EventHandler handler = null;
handler = delegate
{
    button1.Click -= handler;
    ...
};
button1.Click += handler;

独自のイベント定義

イベントに独自のデータを持たせる必要があるときには、System.EventArgsを継承してクラスを作成します。EventArgs クラス (System) | MSDN

class SampleEventArgs : System.EventArgs
{
    int position;

    public SampleEventArgs( int position )
    {
        this.position = position;
    }
}

イベント用のデリゲートの定義

イベントを処理するためのデリゲートを定義します。第2引数に、作成したイベントのクラスを持たせます。

delegate void SampleEventHandler( object source, SampleEventArgs e );

EventHandler<TEventArgs>デリゲートを用いれば、独自のデリゲートの定義は不要です。その場合にはdelegateの定義を記述せず、それを用いる側で次のようにイベント宣言します。

event EventHandler<SampleEventArgs> CustomEvent;

イベントの宣言 (declaring events)

先に定義したデリゲートにevent修飾子を適用することで、イベントを宣言できます。event (C# リファレンス) | MSDN

class MyClass
{
    event SampleEventHandler CustomEvent; // イベント宣言

    void Method()
    {
        if( CustomEvent != null )
        {
            // イベント クラスのインスタンスの生成
            SampleEventArgs args = new SampleEventArgs( 123 );

            // イベントの発生 (Raising an Event)
            CustomEvent( this, args );
        }
    }
}

イベントを発生するときは、そのイベントがnullではないことを確認します。イベントの実体は関数ポインタに近く、そこに呼び出し先が登録されていない状態で呼び出すとNullReferenceException例外が発生します。c# - NullReferenceException when triggering event - Stack Overflow

この、呼び出し先が登録されているならば呼び出す処理は次のようにも記述でき、これを用いないとC# 6.0以降では「IDE1005 C# デリゲート呼び出しを簡略化できます。」と通知されます。

CustomEvent?.Invoke(this, new SampleEventArgs( 123 ));

これを使用する側は、次のようにすることでイベントを受け取れます。

class Handler
{
    MyClass myClass;

    Handler()
    {
        myClass = new MyClass();
        myClass.CustomEvent += new SampleEventHandler( Callback );
    }

    void Callback( object sender, EventArgs e )
    {
    }
}

イベントを発生できる場所

イベントは特殊なマルチキャスト デリゲートであり、それが宣言されたクラスまたは構造体の内部からしか発生できません。event (C# リファレンス) | MSDN

class MyClass
{
    public event EventHandler CustomEvent;
    public void Method()
    {
        if( CustomEvent != null )
        {
            this.CustomEvent(this, EventArgs.Empty); // OK
        }
    }
}

class Handler
{
    void Method()
    {
        MyClass myClass = new MyClass();
        myClass.CustomEvent(myClass, EventArgs.Empty); // CS0070 イベント 'MyClass.CustomEvent' は、+= または -= の左側にのみ表示されます

        myClass.CustomEvent += delegate (object sender, EventArgs e) { }; // OK
        myClass.Method(); // OK
    }
}

イベント発生時に渡すべきデータがないならば、EventArgs.Emptyフィールドを渡します。 EventArgs.Empty フィールド (System) | MSDN EventArgs.Empty instead of null? - c# - Why use EventArgs.Empty instead of null? - Stack Overflow

静的イベント (static event)

eventをstaticとすることで、静的なイベントとして宣言できます。

delegate void MyEvent();

class MyClass
{
    public static event MyEvent CustomEvent; // 静的イベント

    public static void Method() // 静的メソッド
    {
        if( CustomEvent != null )
        {
            CustomEvent(); // イベントの発生
        }
    }
}

イベントを処理する側では、クラス名で登録します。

MyClass.CustomEvent += delegate()
{
};

カスタム イベント アクセサ (custom event accessor)

カスタム イベント アクセサで、イベントへのアクセスを制御できます。たとえば、

public event SampleEventHandler CustomEvent;

のようなイベント宣言があったとき、これはコンパイラによって次のように展開されます。

private SampleEventHandler _CustomEvent;

public event SampleEventHandler CustomEvent
{
    add
    {
        _CustomEvent += value;
    }

    remove
    {
        _CustomEvent -= value;
    }
}

そこでこの定義を独自に実装することで、イベント処理をカスタマイズできます。

参考

参考書

MSDN (Microsoft Developer Network) から検索