delegateキーワードにより宣言したデリゲートやライブラリで提供されるデリゲートは暗黙的にMulticastDelegateクラスを継承していて、デリゲートの基本的な機能はその基底クラスであるDelegateクラスにより提供されます。 ここではDelegateクラスの持ついくつかのメソッド・メンバとデリゲートの機能について見ていきます。
マルチキャストとデリゲートの連結・削除 (Combine, Remove)
イベントのマルチキャストで解説したとおり、デリゲートには一つのデリゲートで複数のメソッドを呼び出すことが出来るようになっています。 これは、一つのデリゲートを別のデリゲートと連結することによりマルチキャストするデリゲートを作成することで実現されます。
デリゲートの連結にはCombineメソッド、デリゲートからのメソッドの削除にはRemoveメソッドもしくはRemoveAllメソッドを使います。 なお、Delegateクラスは不変クラスです。 そのため、これらのメソッドは静的メソッドとして存在し、個々のデリゲートインスタンスへの変更は行われません。 メソッドを呼び出すと、連結・削除を行った結果を含む新たなインスタンスを返すようになっています。 また、これらのメソッドの戻り値の型はSystem.Delegateとなるため、連結・削除を行ったデリゲートの呼び出しを行う場合は適切な型にキャストしなおす必要があります。
以下はCombineメソッドとRemoveメソッドを使ってデリゲートの連結と削除を行う例です。
※DelegateがキーワードDelegateと型System.Delegateのどちらを表すかあいまいになってしまうため、この例ではDelegateをかぎ括弧 [ ] でくくることにより識別子として扱うようにエスケープしています。
なお、C#ではイベントの場合と同様に+=演算子と-=演算子を使ってデリゲートの連結・削除を行うことが出来るようになっています。
連結されたデリゲートの呼び出しリストに同一のメソッドが複数存在する場合、Removeメソッドはそのうちの最後にあるものを削除します。 RemoveAllメソッドでは、一致する全てのメソッド呼び出しを削除します。
Removeメソッド・RemoveAllメソッドによって削除を行った結果としてデリゲートの呼び出しリストが空になる場合、戻り値はnull/Nothingとなります。 従って、Remove・RemoveAllメソッドを使って呼び出しリストが空のデリゲートを作成することは出来ません。 デリゲートは常に1つ以上のメソッドを呼び出しリストに含みます。
呼び出されるメソッド・インスタンスの取得 (Method, Target, GetInvocationList)
デリゲートのMethodプロパティを参照することでデリゲートに指定されているメソッドのMethodInfoを取得することが出来ます。 また、Targetプロパティを参照することで、呼び出される対象のインスタンスを取得することが出来ます。
なお、静的(共有)メソッドの場合はTargetプロパティがnull(Nothing)になります。 MethodInfo.IsStaticプロパティを参照することでメソッドが静的かどうか調べることが出来ます。
また、連結されたデリゲートの場合、MethodプロパティとTargetプロパティはデリゲートの呼び出しリストにある一番最後のメソッドとインスタンスを返します。 呼び出しリストに含まれる個々のデリゲートを取得するにはGetInvocationListメソッドを使います。
複製 (Clone)
デリゲートを複製するにはCloneメソッドが使えます。 このメソッドは、簡易コピーを作成します。
複数のメソッドが指定されているデリゲートも同様に複製されます。
等価性の比較 (Equals, ==演算子, !=演算子)
二つのデリゲートの等価性を比較するには、Equalsメソッド、等価演算子==、不等価演算子!=(<>)が使えます。 デリゲート同士の比較では
- 二つのデリゲートの型が同じであり
- かつ、二つのデリゲートの呼び出しリストにある個々のメソッドが、すべて同じインスタンスの同じメソッドである場合
等しいと判断されます。 なお、.NET Framework 1.xではデリゲートの型が異なっていても呼び出しリストの内容が等しい場合、二つのデリゲートは等しいと判断されます。 また、等価演算子・不等価演算子では両辺のデリゲートが同じ型でないと比較出来ませんが、Equalsメソッドでは異なるデリゲート型やデリゲート以外の型でも比較することは出来ます(この場合、戻り値はfalseとなります)。
メソッドの非同期呼び出し (BeginInvoke, EndInvoke)
デリゲートを使ってメソッドを呼び出す場合、特殊なメソッドであるBeginInvokeメソッドとEndInvokeメソッドを使うことで非同期的にメソッドを呼び出すことが出来ます。
BeginInvokeメソッドは、呼び出すと非同期的にメソッドの実行を開始したのち、すぐにIAsyncResultを返します。 このインターフェイスには、非同期的に実行したメソッドの処理が完了しているかどうかを知るためのプロパティIsCompletedや、メソッドの終了を待機するための待機ハンドルを参照するプロパティAsyncWaitHandleなどが用意されています。 BeginInvokeメソッドが返すIAsyncResultを参照することにより、非同期操作の完了を待機することができます。
WaitHandleについてはSystem.Threading.WaitHandleで解説しています。
非同期呼び出しはEndIvokeメソッドを呼び出すことで完了します。 EndInvokeメソッドは引数にIAsyncResultを取るようになっていて、非同期呼び出しの開始時にBeginInvokeメソッドが返すIAsyncResultを渡します。 EndInvokeメソッドによって非同期呼び出しが完了すると、非同期呼び出ししたメソッドの戻り値がEndInvokeメソッドの戻り値として返されます。 EndInvokeメソッドはメソッドの処理が終わっていない場合でも呼び出すことが出来、その場合は処理が完了するまで待機してから結果を返します。
以下は、結果を計算するのに時間がかかる重い処理を行うメソッドComputeを用意し、デリゲートを使って非同期的に呼び出す例です。 また、呼び出し側では処理が完了するまでIAsyncResult.AsyncWaitHandleを使って待機するようにしています。
比較のために、デリゲートを使った通常の(同期的な)呼び出しを行うコードを記述すると次のようになります。
BeginInvokeメソッドは、通常のデリゲート呼び出しの場合に加えてさらに二つの引数を要求します。 例えば、引数の無いActionデリゲートでは、BeginInvokeを呼び出す場合2の引数を指定する必要があります。 同様に引数が1つあるActionデリゲートでは、3つの引数を指定してBeginInvokeを呼び出します。
デリゲートの引数と、対応するBeginInvokeメソッドのシグネチャは次のようになります。
デリゲートのシグネチャ | BeginInvokeのシグネチャ |
---|---|
void Action() | IAsyncResult BeginInvoke(AsyncCallback, object) |
void Action(int) | IAsyncResult BeginInvoke(int, AsyncCallback, object) |
void Action(string, string) | IAsyncResult BeginInvoke(string, string, AsyncCallback, object) |
int Func(int) | IAsyncResult BeginInvoke(int, AsyncCallback, object) |
void EventHandler(object, EventArgs) | IAsyncResult BeginInvoke(object, EventArgs, AsyncCallback, object) |
このように要求される二つの引数は、非同期呼び出しが終了した際に呼び出されるコールバックとパラメータを指定する際に使用されます。 コールバックが不要な場合は、これら二つの引数にnull/Nothingを指定することができます。
EndInvokeメソッドは常に1つの引数IAsyncResultを要求しますが、戻り値はBeginInvokeメソッドと同様にデリゲートのシグネチャによって変わります。
デリゲートのシグネチャ | EndInvokeのシグネチャ |
---|---|
void Action() | void EndInvoke(IAsyncResult) |
void Action(string, string) | void EndInvoke(IAsyncResult) |
int Func() | int EndInvoke(IAsyncResult) |
double Func(int) | double EndInvoke(IAsyncResult) |
非同期呼び出し完了のコールバック
BeginInvokeメソッドの最後の二つの引数には、非同期呼び出しが終了した際に呼び出されるコールバックメソッドを指定するデリゲートAsyncCallbackと、コールバックメソッドに渡す任意の引数を指定出来ます。 この引数は、AsyncCallbackに渡されるIAsyncResultのAsyncStateプロパティで参照出来ます。
次の例は、上記の例をコールバックメソッドを使うように書き換えたものです。 この例では、非同期呼び出しによって処理を開始した後、完了まで待機せずに続けて別の処理を平行して行っています。 また、EndInvokeの呼び出しと戻り値の取得・表示はコールバックメソッドの側で行っています。 この例では、非同期呼び出しを開始したデリゲートを取得するために、コールバックメソッドに渡されたIAsyncResultをAsyncResultクラス (System.Runtime.Remoting.Messaging)にキャストし、AsyncDelegateプロパティを参照しています。
非同期処理に関するドキュメント
非同期処理およびBeginInvoke, EndInvoke, IAsyncResultについてより詳しく知るためには、以下のドキュメントを参照してください。
また、.NET Framework 4以降では、System.Threading.Tasks名前空間のクラス群を使うことで非同期処理をより簡単に実装することができるようになっています。 詳しくは.NET Framework の並列プログラミングなどを参照してください。