昨日(前のエントリで)、メインスレッドで動作しているUIのコンポーネントを別スレッドから呼び出す為にDelegateとSystem.Windows.Forms.Control.Invokeを使ったわけだが、今日また色々疑問が出てきたりもう少し調べたりしてだめだ書かないとわけわからなくなってきたから書く。
連載:C#入門 第17回 処理を委譲するdelegateにDelegateについてのコードがたくさん書いてあるが、4ページ目の2番目のコードで、デリゲートを返すgetDelegate()というpublicな関数を作っている所でなんか違和感があった。
関数への参照を既に入れてあるデリゲート自体を返すって、それカプセル化とかどうなんだ?言語仕様的にできるけど、実際使う処理なんだろうか?
少なくとも、俺が昨日作ったPhidgetRFIDの起こすイベントからFormの動いてるメインスレッドに委譲するためのDelegateでは、デリゲート自体を返す必要は無かった。
もうちょっと調べてみると、特集「私がJavaからC#に乗り換えた10の理由」について – Insider.NETというフォーラムで色々書かれていて、
・デリゲートは、関数へのポインタみたいな物だが、正確にはあるクラスorインスタンスのもつ関数へのポインタ(参照)である
・メソッドの持ち主のインスタンスへのポインタも持っている。また、実行時には持ち主のインスタンスのコンテキストで実行される
・デリゲートの関数ポインタはリストになっていて、参照先を複数持たせて呼び出し時に同時実行させられる(マルチキャストデリゲート)だが引数の型は揃えなければならない。
・staticな関数への参照としてデリゲートを作った場合、メソッド持ち主のインスタンスへのポインタはnullになる。また、実行コンテキストはメソッド持ち主のクラスオブジェクトのコンテキストになる
・返り値を持つ関数を複数登録した時、1個目の値は取れる。他は全部破棄される。
あと
「あるインスタンスのあるメソッド」に対するデリゲートの場合は、デリゲート自身が”委譲先インスタンスへの参照”と”委譲先メソッドへの参照(=リフレクションのメソッドオブジェクト=メンバ関数ポインタ)”を保持しておりますので、委譲先インスタンスのコンテキストで委譲先メソッドを呼び出します。
「あるクラスのあるメソッド(staticメソッド)」に対するデリゲートの場合は、”委譲先インスタンスへの参照”はnull参照になっており、委譲先メソッドへの参照のみで、委譲先メソッドの所属するクラスオブジェクトのコンテキストで、委譲先メソッドが呼び出されます。(メソッド参照があれば、該当するクラスオブジェクトはリフレクションにより取得可能ですし)
だそうだ。
とても概念的で難しいが、なんとなくデリゲートの実体と機能がわかってきた。
とにかく、
・関数をリストできて、しかもオブジェクトになる
・関数の持ち主のインスタンスもわかる
・実行コンテキストが、関数の所属しているインスタンスroクラスオブジェクトになる
(コンテキスト=スレッドみたいなもの、ととりあえず思っていたが、どうも違う気がする。以前ちょっと調べた組み込みRTOSのコンテキストスイッチとかもTimer回してスレッド切り替える感じだったし)
・System.Windows.Forms.Control.Invokeに引数としてデリゲートを渡すと、任意のスレッドで実行できる
だから何が言いたいかというと、デリゲートを返すgetDelegate();ってメソッドは、デリゲートそのもを渡してしまったら既に追加されてる奴が消されてしまったりとか、Invokeとかで実行コンテキストも自由にされてしまったり、余計な権限を渡してるんじゃないかと思った。
addDelegate(関数名);とかdelDelegate(関数名);というメソッドだけpublicに公開する方がいいんじゃないか?
************
と思いながらメシくってたんだけど、よくよく考えたら関数はオブジェクトじゃないから引数に渡せないんだよな。
コントロールのイベントハンドラを追加&削除も今見ればデリゲートの関数ポインタリストに += 演算子でポインタを追加してるのがわかった。
このthis.button1.Clickはデリゲートへのアクセサなわけだ。
public System.EventHandler Click{
get{ return this.hogehogeDelegate; }
set{ this.hogehogeDelegate = value; }
}
とか実装されてるわけだ。ちゃんとカプセル化されてる気がしてきた。
今書きながら出した結論なんだが
■とにかく、イベントハンドラ(実体はデリゲート)へのアクセサをpublicで公開して、 += や -= 演算子で関数ポインタリストを設定できるようにするのは合点がいった。これは今後の実装で意識して取り入れてみよう。
今までコールバック関数とかばっかで実装してたしな。
■じゃあ、昨日のPhidgetRFIDで実装したような、FormのUIへ書き込むつまりメインスレッドで実行するべき関数をDelegateとControl.Invoke()で委譲する処理はどう書いてカプセル化するのが適切なんだろう?
この場合は、デリゲートを返す必要は無いと思う。昨日の通りpublic void message_writeLine(string str){ …. }という関数の中でデリゲート生成とInvoke()とかもやってあげれば良くて、message_writeLine()関数だけをpublicに公開すれば良い。
メインスレッド以外で実行するユースケースは無いはずだから、決め打ちで良いと思った次第。
そんなわけで、一応納得した。コンテキストとスレッドの違いというかコンテキストってなんなんだか余計わからないが。
というわけで、デリゲートについて結構わかったので、今ならWindowsのローレベルフックもできる気がする。KeyPlayerに、「ウィンドウがアクティブでなくてもキーボードショートカットで操作出来る」という機能を付けたい。
とにかくキーイベントのデリゲート本体を見つけてきて、自前の関数を += new デリゲート型(自前の関数);ってやればいいんだろうと思ってもう一度[HOWTO] Visual C# .NET で Windows フックを設定する方法を見たら
あー[DllInport]とかまた……
でも今見直したらやっぱりDelegate使ってるし、山は一つ越えたな。よかったよかった