イベントとデリゲート

先月うちのプロジェクトに入ってきた人と,最近よく一緒に昼食に行きます.彼(B君)は自分よりも年下で,分からないことは恥ずかしがらずに積極的に聞いてくるタイプです.自分は,聞かれない限り自分から進んで説明しないタイプなので,彼みたいなタイプとは相性がいいようです.
彼はこれまでJavaをやっていて,C#は初めてとのこと.


B君:「C#Javaって大体一緒ですね?」
うん,まぁ最初のうちはそう思ってていいよ.でも結構違いもあるよ.
B君:「え?どんな違いがあるんですか?」
たとえばイベントとかデリゲートとかって仕組みはC#の最初のバージョンからあるよね.
B君:「なんですか?それ」


こういう素朴な質問をされると,冷や汗が出てきますね.うまく答えられるかな?


自分:「まずデリゲートだけど,これはよく"関数ポインタ"みたいなもの,って言われるんだよね.関数ポインタは知ってる?」
B君:「?」
自分:「えっと……じゃあJavaScriptで,関数はデータ型だ,ってことは分かる?たとえば関数hogeが定義されているとき,var a = hoge; a(); みたいな事ができる」
B君:「はい,それは知ってます」
自分:「C#のデリゲート/イベントはそれと似たような考え方なんです.JavaScriptは弱い型付けの言語だけど,C#は強い型付けをもつ言語だから,変数の宣言が必要になる.まずデリゲートで引数と戻り値を持つメソッドの型のようなものを作り,その型のようなものを利用してイベントを定義する.
自分:「例えば,あるオブジェクト/クラスが自分が更新されたことを表すUpdatedというイベントを定義したいとする.その際はまずそのためのデリゲートとイベントを定義します.デリゲートがもつ引数と戻り値はほんとうは何でもいいんだけど,一般的には以下のようなものを使う.

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

このデリゲートを使って,イベントを定義する

public event UpdateEventHandler Updated;

自分:「このイベントを定義したオブジェクト/クラスは,自分の状態が変更されたらUpdatedイベントを呼び出す.しかしここでポイントとなるのは,イベントUpdatedには実体がないということです.実体がない(null)のままでイベント呼び出しを行なうと例外が発生する.しかしコンパイル自体は成功する.
自分:「イベントが発生したことを受信したいオブジェクトは,UpdateEventHandlerと同じシグニチャをもつメソッドを用意し,イベントと束縛する.こうすることによって送信側のUpdatedイベント呼び出しが受信側のメソッド呼び出しに変換される……っていうのがイベントの仕組み/からくりになります.
自分:「なぜこんなめんどくさいことをやるのかというと,たとえば送信側のクラスと受信側のクラスが別アセンブリにある場合を考えれば分かると思う.JavaでもObserverパターンを使って同種のことを実現できるけど,その場合受信側のクラスについて,(例えばあるクラスを継承している,などの)ある種の制約が必要になってしまうけど,アセンブリが別だと下手をすると互いに相手を参照するような,強結合な構造になってしまうかもしれない.イベントはそれを言語レベルで,洗練されたやり方で解決してるんだよねー」


……まぁ,こんな感じで説明してみました.
実際に説明しようとすると,分かっているはずのデリゲート・イベントの意味とか仕組みとかについて,ものすごく曖昧でいい加減な理解をしてたんだ,ってことがよくわかりますね(^^;;
あとで『プログラミング .NET Framework』の復習しよっと.

プログラミングMicrosoft .NET Framework (マイクロソフト公式解説書)

プログラミングMicrosoft .NET Framework (マイクロソフト公式解説書)