旧来のVBではイベントは言語の機能として提供されていましたが、.NET Frameworkにおいてはデリゲートの機能を使ってイベントが実現されます。 イベントを検知・受信する側は、デリゲートを使ってコールバック(イベントハンドラ)となるメソッドを指定し、イベントを発生・通知させる側は指定されたデリゲートを使ってイベントハンドラを呼び出します。 これによってイベントの発生と受信の機構が実現されます。
イベントとデリゲート
デリゲートだけを使ってイベント機構を実装することも出来ますが、C#ではeventキーワード、VBではEventステートメントを使うことでイベント機構をより簡単に実装できるようになっています。 以下は、クラスにイベントを実装した簡単な例です。
まずは、イベントを発生・通知する側から見ていきます。 この例のWaitAndNotifyクラスは待機を行う機能と、待機の開始と完了の時点でイベントを発生させて通知を行う機能を持っています。 開始と完了のイベントは、WaitStartイベントおよびWaitDoneイベントとして外部に公開しています。 この例では、イベントの型としてEventHandlerデリゲートを使っています。 このデリゲートはイベントを発生させたインスタンスを表すobject型の引数と、発生したイベントの内容を表すEventArgs型の引数をとるメソッドを表します。 イベントを受信する側は、このシグネチャと一致するメソッドをイベントハンドラとして指定することで、WaitStartとWaitDoneのイベントの発生を検知することが出来るようになります。
実際にイベントを発生させているのがWaitAndNotify.Waitメソッドの部分です。 C#ではメソッド呼び出しと同様に、VBではRaiseEventステートメントを使ってイベントに割り当てられているハンドラを呼び出しています。 EventHandlerデリゲートのシグネチャに合わせて、引数に現在のインスタンス(this, Me)と空のイベントを表すEventArgs.Emptyを指定して呼び出しています。 ここで指定した引数は、イベントハンドラとなるメソッドに引き渡されることになります。
続いて、イベントを検知・受信する側について見ていきます。 C#では+=演算子、VBではAddHandlerステートメントを使うことで、WaitStartイベントとWaitDoneイベントにそれぞれのイベントハンドラとなるメソッドのデリゲートを指定しています。 イベントが発生すると、これらのメソッドが呼び出されることになります。 イベントが発生した際に呼び出されるメソッドがWaitStartHandlerとWaitDoneHandlerです。
このようにしてイベントの発生と受信の機構を実装することが出来ます。 イベントとデリゲートの関係については、以下のドキュメントでより詳しく解説されています。
なお、上記の例では.NET Frameworkのガイドラインに従いイベントのデリゲートにEventHandlerを使用していますが、独自に宣言したデリゲートなどEventHandler以外のデリゲートを使ってもイベントを実装することも出来ます。 イベントを発生・通知させる側と検知・受信する側で同じシグネチャのデリゲートが使われていれば良く、引数の数や型はイベントとして通知したい内容に合わせて自由に選択することが出来ます。 以下は、上記の例をActionデリゲートを使って書き換えたものです。
イベント引数
.NET Frameworkのガイドラインに従ったイベントの場合、二つ目の引数にEventArgsクラスから派生した任意のクラスをイベント引数として指定します。 イベントが発生したときの状況や、追加の情報などはイベント引数としてイベントを受信する側に伝える事が出来ます。 先の例を少し変えて、イベント引数を使ってイベントが発生した時刻を取得できるようにしてみます。
この例では、EventArgsクラスを継承したWaitEventArgsクラスを作成し、DateTime型の読み取り専用フィールドを追加しています。 読み取り専用にしているのは、イベントハンドラの側で値を変更できないようにするためです。 また、WaitEventArgsクラスを使ったイベントを定義するために、EventHandler<TEventArgs>デリゲートを使っています。 このデリゲートの型パラメータTEventArgsには、EventArgsから派生した任意の型を指定することが出来ます。 この例ではEventHandler<WaitEventArgs>として使用しています。
このようにして、独自に作成したWaitEventArgsを使ってイベントを発生させることが出来るようになりました。 これに合わせて、イベントハンドラの引数もWaitEventArgsにすることで発生したイベントを受信できるようになります。
EventHandler<TEventArgs>の代わりに、独自にデリゲートを宣言することも出来ます。 イベントハンドラとなるデリゲートの場合でもデリゲートには任意の名前を指定することは出来ますが、ガイドラインではイベントハンドラとなるデリゲートであることが分かるよう、デリゲートの名前はEventHandlerで終わるようにすることが推奨されています。 以下の例では、WaitEventArgsをイベント引数とするデリゲートWaitEventHandlerを使ってイベントを定義しています。
マルチキャスト
イベントハンドラに指定するメソッドは、一つだけではなく複数指定することが出来ます。 これはデリゲートの持つマルチキャストの機能により実現されるものです。 既に解説した一つのイベントハンドラを指定する場合と同様、C#では+=演算子、VBではAddHandlerステートメントを使って一つのイベントに複数のイベントハンドラを指定する事が出来ます。
結果から分かるとおり、指定したすべてのイベントハンドラが呼び出されています。 なお、複数のイベントハンドラを指定した場合は、指定した順(イベントに追加した順)でイベントハンドラが呼び出されるようになります。
この様にして追加した複数のイベントハンドラから特定のハンドラを削除したい場合は、C#では-=演算子、VBではRemoveHandlerステートメントを使います。 当然、削除したイベントハンドラはイベントが発生しても呼び出されなくなります。
なお、一つのイベントに同じメソッドのイベントハンドラが複数追加されている場合、削除の際に同じメソッドのイベントハンドラが一度に削除されます。