イベントはデリゲートに基づくため、そちらも理解する必要があります。
EventArgsクラスは、イベントに関するデータを格納するクラスの基本クラスです。EventArgs クラス - EventArgs クラス (System) | Microsoft Learn
[System.Runtime.InteropServices.ComVisible(true)] [System.Serializable] public delegate void EventHandler( object sender, // イベントの元 (source) EventArgs e // イベント データを含まないオブジェクト );EventHandler Delegate (System) | Microsoft Learn
public delegate void CancelEventHandler( object sender, CancelEventArgs e )CancelEventHandler デリゲート (System.ComponentModel) | MSDN
イベントに加算代入演算子でハンドラのオブジェクトを代入することで、イベントにサブスクライバー (subscriber) を登録できます。
button.Click += new EventHandler( button_Click );
このときコールバックメソッドは、
void button_Click( object sender, EventArgs e )
{
...
}
のように定義します。ところでC# 2.0以降ならば、new EventHandler()
を省略して次のようにも記述できます。
button.Click += button_Click;
登録するのが他のクラスのメソッドであるとき、そのクラスのインスタンスがnullだと「インスタンス メソッドへのデリゲートに null の 'this' を指定することはできません。」としてArgumentExceptionが投げられます。
MyClass myClass = null;
button.Click += myClass.Method; // ArgumentException
同一のイベントハンドラをほかにも使用するならば、
EventHandler clickEventHandler = new EventHandler( button_Click ); button1.Click += clickEventHandler; button2.Click += clickEventHandler;
のようにも記述できます。
同一のイベントに同一のハンドラを登録すると、1回のイベントで複数回ハンドラが呼び出されます。
EventHandler clickEventHandler = new EventHandler( button_Click ); button1.Click += clickEventHandler; button1.Click += clickEventHandler;
登録されているハンドラは、リフレクションを用いて確認できます。c# - How to obtain the invocation list of any event - Stack Overflow
Control control = button1; string eventName = "Click"; PropertyInfo property = typeof(Component).GetProperty("Events", BindingFlags.NonPublic | BindingFlags.Instance); EventHandlerList handlerList = (EventHandlerList)property.GetValue(control); FieldInfo field = typeof(Control).GetField("Event" + eventName, BindingFlags.NonPublic | BindingFlags.Static); object key = field.GetValue(control); EventHandler handler = (EventHandler)handlerList[key]; Delegate[] invocationList = handler.GetInvocationList();
匿名メソッドを使用することで、コールバックメソッドを省略できます。
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;
登録されていない、または解除済みのハンドラのオブジェクトを解除したときは、何も処理されません。
すべてのハンドラが解除されると、イベントのインスタンスはnullに戻ります。
イベントに独自のデータを持たせる必要があるときには、System.EventArgsを継承してクラスを作成します。EventArgs クラス (System) | MSDN
class SampleEventArgs : System.EventArgs { int data; public SampleEventArgs( int data) { this.data= data; } }
イベントを処理するためのデリゲートを定義します。第2引数に、作成したイベントのクラスを持たせます。
delegate void SampleEventHandler( object source, SampleEventArgs e );
EventHandler<TEventArgs>デリゲートを用いれば、独自のデリゲートの定義は不要です。その場合にはdelegateの定義を記述せず、それを用いる側で次のようにイベント宣言します。
event EventHandler<SampleEventArgs> CustomEvent;
先に定義したデリゲートに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 ) { 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 } }
イベント発生時に渡すべきデータがないならば、ハンドラがnullであることを想定しなくても良いように、EventArgs.Emptyフィールドを渡します。 EventArgs.Empty フィールド (System) | MSDN EventArgs.Empty instead of null? - c# - Why use EventArgs.Empty instead of null? - Stack Overflow
このEventArgs.Emptyを渡すことは、new EventArgs()
を渡すことと同義です。Empty - eventargs.cs
eventをstaticとすることで、静的なイベントとして宣言できます。
delegate void MyEvent(); class MyClass { public static event MyEvent CustomEvent; // 静的イベント public static void Method() // 静的メソッド { if( CustomEvent != null ) { CustomEvent(); // イベントの発生 } } }
イベントを処理する側では、クラス名で登録します。
MyClass.CustomEvent += delegate() { };
カスタム イベント アクセサで、イベントへのアクセスを制御できます。たとえば、
public event SampleEventHandler CustomEvent;
のようなイベント宣言があったとき、これはコンパイラによって次のように展開されます。
private SampleEventHandler _CustomEvent; public event SampleEventHandler CustomEvent { add { _CustomEvent += value; } remove { _CustomEvent -= value; } }
そこでこの定義を独自に実装することで、イベント処理をカスタマイズできます。
イベントが宣言されているクラスを継承したとき、派生クラスから基本クラスのイベントを発生することはできません。よってその必要があるときは、次のような方法で対処します。
Reactive Extensionsは、監視可能 (observable) シーケンスとLINQ様式のクエリ演算子を使用して、非同期やイベント ベースのプログラムを作成できるライブラリです。