昨日(前のエントリで)、メインスレッドで動作している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に公開する方がいいんじゃないか?
Read more