デリゲート入門 | |
|
昔のPCのコーディング
世界初の8 bitマイクロプロセッサはIntelの8008と言うチップでプログラムは数字の羅列の
機械語で書いていました。 ジャンプ命令などは飛び先のメモリの番地を直接書くわけです。 この為プログラマは常に紙に書いたメモリマップを持っていました。 そのうちにニーモニックなるものが出てきて、数字の羅列の機械語から命令などは より人間にわかり易いアルファベットによる文字になりました。 ジャンプ先などの番地も数字でアドレスを書くのではなく、文字によるラベルが使えるように なりプログラミングの作業性が一段と向上しました。 文字やラベルはアッセンブラと呼ばれるプログラムがCPUが理解できる機械語に直すのです。
デリゲートはメソドの参照を保持する
C#やVB.NETでメソドと呼ばれる物はプログラムの塊でメモリー上の何処かに有ります。
メソドを呼び出すと言うことは機械語で言えばジャンプ命令が行われると言うことです。 ただしFrameWorkのプログラマはそのメソドがメモリー上の何処に有るかなどということは知らなくても良いのです。 番地に相当する名前を書くだけでメソドを呼び出すことがジャンプ命令などと夢にも思っては居ません。 現代のプログラマにとってはジャンプ命令は禁句でジャンプ命令は構造化の敵なのです。 さてデリゲートですが、デリゲートはメソドの参照を持つクラスです。 参照とは平たく言うとアドレスです。 つまりデリゲートは名前でなくメソドをアドレスで呼び出すことの出来るクラスなのです。 回りまわって又昔に戻ったのです。 クラスに有るメソドの参照を(アドレス)をデリゲートに登録しておくと、そのメソドを デリゲートで呼び出すことが出来るのです。 厳密にはデリゲートが保持するのはクラスのインスタンスのメソドの参照です。
なぜアドレスで呼び出すのか?
何故せっかく名前で呼び出せるものを又昔の様にアドレスで呼び出そうとするので有ろうか。例えばある大きなファイルを読み書きする時その処理をメインスレッドで行うと、 読み書きしている間スレッドが固まってしまいます。 これを避けるために、通常読み書きはメインスレッドとは別のスレッドを作成して、非同期に 操作が行われます。 こうするとメインスレッド上で他の作業の継続が可能になるのです。 ただし多くの場合呼び出し側のスレッドでも読み書きの終了や、読み込んだファイルの内容が 知りたい場合が有ります。 つまり読み書きを指示したスレッドの指定したメソドに読み書きをしているスレッドから報告(コールバック) が欲しいのです。 それには新しく作成された読み書きを行うスレッドに対して、ここに通知しなさいという何らかの情報を渡す 必要があります。 この為に使われるのが通知すべきメソドのアドレスです。 確かにメソドの名前を渡す方法も考えられるが、読み書きを行うスレッドを作成したスレッドも複数の インスタンスを持っている可能性が有り、名前を渡す場合は何らかの方法でインスタンスを区別 しなければなりません。 アドレスは複数のインスタンスが有ってもインスタンス毎に異なります。 すなわちアドレスを使うと、目的のインスタンスのメソドを確実に渡すことが出来ます。 この考え方はC#やVB.NETのイベントの方法にも取られています。 イベントもデリゲートそ使って行われるのです。
デリゲートのプロトタイプ宣言
ただしデリゲートが単純にメソドのアドレスを保持していて、そのアドレスにジャンプしただけでは
不具合が生じます。 メソドには引数を取るものや戻り値を返すものが有からです。 そこで目的のメソドのアドレスを保持することが出来るデリゲートクラスは必ずそのメソドと同じ 戻り値や引数を設定できる方法で宣言します。(デリゲートのプロトタイプ宣言)。 コンパイラはそのプロトタイプを見て、そのデリゲートが目的のメソドの引数や戻り値にマッチ しているか否かを確認し安全性を保障します。 もしそれが一致していなければビルド時にエラーを報告するはずです。 つまりデリゲートは単にクラスのインスタンスのメソドのアドレスを保持するだけではなく 引数と返値の受け渡しも行なうのです。
デリゲートの実際例
実際のデリゲートを見てみましょう。
delegate void dlgWriteString(string msg); ---C#
これは文字列を引数に取り返値を取らないデリゲートのクラスの宣言である(プロトタイプ)です。
このデリゲートクラスは同じ引数を持ち同じ返値を持つメソドであればどの様なメソドで有っても その参照(アドレス)を保持できます。 このデリゲートを使って C#のコード
public void WriteMessage(string msg) VB2005のコード
Class WriteToScreen
このクラスのWriteMessageメソドを呼び出してみます
デリゲートはクラスで有る為インスタンスを作り、コンストラクタには保持すべきクラスのインスタンスの メソドを指定します。 C#のコード
dlgWriteString dws = new dlgWriteString(ws.WriteMessage); VB2005のコード
Dim dws As New dlgWriteString(AddressOf ws.WriteMessage)
デリゲートを実行するには
dws(msg); dws(msg) とすれば良いのです。
Action<T>
さてデリゲートの宣言(プロトタイプ)ですが
FrameWorkには既定の宣言(既に行われている)が有ります。 Action<T>と呼ばれるもので1つの引数と戻り値が無いデリゲートが宣言されています。 (Action<T>はFrameWork2.0以上で使用できる。FrameWork3.0以上では更に色々な既定のデリゲートが有る。) <T>のTにはタイプが入る、上のデリゲートは C#のコード
Action<string> wm = new Action<string>(ws.WriteMessage); VB2005のコード
Dim wm As New Action(Of String)(AddressOf ws.WriteMessage)
となります delegate void dlgWriteString(string msg); --- C# Delegate Sub dlgWriteString(ByVal msg As String) ---VB2005 のプロトタイプは必要なくなります。
デリゲートと匿名メソド
さてもう少し進めよう、C#には匿名メソドと言うものが有る(残念ながらVB2005には無い)。
public void WriteMessage(string msg){ Console.WriteLine(msg); }
はWriteMessageという名前を持っています。
匿名メソドとはこの名前を持たないメソドです。 これを使うと delegate void dlgAwrite(); と引数を持たないデリゲートを宣言して、 dlgAwrite da = delegate() {Console.WriteLine(msg);}; da(); の様にインラインでメソドを使用することが可能となります。 この匿名メソドによるデリゲートそれが実行されるクラスのスコープを持つために 引数の省略ができてオーバーヘッドが少ない事とデリゲートの為のメソドが同じ場所に 書かれて居る為にコードが見やすい利点があります。 C#のコード
VB2005のコード
Imports System ブログ デリゲート も合わせてお読みください。 |