2010-10-27T22:51:26の更新内容

programming/netfx/delegate/0_abstract/index.wiki.txt

current previous
1,854 1,391
~
${smdncms:title,デリゲートの基本}
${smdncms:title,イベントとデリゲート}
~
${smdncms:keywords,デリゲート,Delegate,AddressOf}
${smdncms:keywords,イベント,デリゲート,Delegate,Event,EventHandler,EventArgs,AddHandler}
 
${smdncms:meta,toc-amazonlivelink-keyword,books-jp,.net framework}
${smdncms:meta,toc-amazonlivelink-keyword,books-jp,.net framework}
-
Visual Basicではコマンドボタンがクリックされたときとか、テキストボックスに文字が入力されたときなどにイベントとしてメッセージが通知されてきました。 Visual Basic .NETでも同様のイベントというものが存在します。 また、Visual Basicでのクラス同様、独自のイベントを定義することができます。
 

        

        
~
#navi(..)
しかしVisual Basic .NETになってからイベントにまつわる部分が大きく変わりました。 個人的には.NETのイベントの方がわかりやすくなったかと思います。 ここではこのイベントと、それと切っても切り離せない関係にあるデリゲートについて考えてみたいと思います。
 

        

        
~
ここではデリゲートの基本的なことについての解説と、デリゲートとメソッドの関わり、デリゲートの宣言など関連する構文などについて解説します。
#googleadunit
 

        

        
~
----
*導入
-
まず、イベント・デリゲートをいきなり使う前に、これからイベントとデリゲートの説明に使うサンプルコードを提示します。
 

        

        
~
*デリゲートとは
#code(vb,指定した時間だけウェイトする){{
+
はじめにデリゲートとは何か、またデリゲートの使い方・動作について理解するため、簡単な例を使って見ていきます。 まずは次のような一定時間待機するだけの単純なコードを用意します。
+

          
+
#tabpage(C#)
+
#code(cs){{
+
using System;
+
using System.Threading;
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    PrintTime();
+

          
+
    Wait(TimeSpan.FromSeconds(3.0));
+

          
+
    PrintTime();
+
  }
+

          
+
  // 時刻を表示するメソッド
+
  static void PrintTime()
+
  {
+
    Console.WriteLine(DateTime.Now.ToString("T"));
+
  }
+

          
+
  // 指定された間隔だけ待機するメソッド
+
  static void Wait(TimeSpan timeout)
+
  {
+
    Thread.Sleep(timeout);
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
 
Imports System
Imports System
 
Imports System.Threading
Imports System.Threading
 

        

        
~
Class Sample
Module Main
~
  Shared Sub Main()

          
~
    PrintTime()
    ' アプリケーションのエントリーポイント
~

          
    Public Function Main(ByVal args() As String) As Integer
~
    Wait(TimeSpan.FromSeconds(3.0))

          
~

          
        Console.WriteLine("Start: {0}", DateTime.Now)
~
    PrintTime()

          
~
  End Sub
        Wait(3000)
~

          

          
~
  ' 時刻を表示するメソッド
        Console.WriteLine("Finished: {0}", DateTime.Now)
~
  Shared Sub PrintTime()

          
~
    Console.WriteLine(DateTime.Now.ToString("T"))
        Return 0
~
  End Sub

          
~

          
    End Function
~
  ' 指定された間隔だけ待機するメソッド

          
~
  Shared Sub Wait(ByVal timeout As TimeSpan)
    ' 指定されたミリ秒だけウェイトする
~
    Thread.Sleep(timeout)
    Public Sub Wait(ByVal millisec As Integer)
~
  End Sub

          
~
End Class
        Thread.Sleep(millisec)
-

          
-
        WaitOver()
-

          
-
    End Sub
-

          
-
    ' ウェイトが終了したことを知らせる
-
    Public Sub WaitOver()
-

          
-
        Console.WriteLine("Wait over.")
-

          
-
    End Sub
-

          
-
End Module
 
}}
}}
+
#tabpage-end
 

        

        
~
#prompt{{
#prompt(実行結果){{
~
14:17:31
Start: 2003/02/21 0:00:39
~
14:17:34
Wait over.
-
Finished: 2003/02/21 0:00:42
-
Press any key to continue
 
}}
}}
 

        

        
~
このコードのWaitメソッドを書き換え、待機が完了したらコールバックを行うようにしてみます。 また、コールバックされるメソッドの指定にはデリゲートを用いることにします。
ソースコードと実行結果とを見比べてもらえばすぐにわかりますが、複雑なことは何もやっていません。 63行目のThread.Sleep()は指定されたミリ秒だけ現在のスレッドを停止するというメソッドです。 Main()メソッドではWait()メソッドを呼び出す前後に、現在の時刻を表示するようにしています。 また、このサンプルでは3000ミリ秒、つまり3秒の間ウェイトするようにしています。 実行結果を見ればわかるように、始まりと終わりの間がちゃんと3秒になっています。
-

          
-
つぎに、このソースコードをデリゲートを使ったものに書き換えることにします。
 

        

        
~
#tabpage(C#)
*デリゲート
~
#code(cs){{
まず最初にデリゲートについて考えてみることにします。 まず、この文章を読んで勉強しようと思われる方の多くは、「イベントは知っているけどデリゲートなんて聞いたことがない」というような方だと思います。 当初僕自身も、デリゲートが理解不能でした。 C++を勉強した方の中には「関数ポインタ」というものをご存じの方もいると思いますが、デリゲートの役割はこの関数ポインタに似ているものといえます(厳密には異なるものです)。
~
using System;

          
~
using System.Threading;
というのも、デリゲートはあるメソッド(共有メソッド及びインスタンスメソッド)を特定する役割を持っているためです。 つまりその役割は、直接特定のメソッドを呼び出すのではなく、デリゲートによってそのデリゲートに指定されているメソッドを間接的に呼び出すことができるわけです。 イベントを発生するときに呼び出すべきメソッドをするためにデリゲートが使われます。 言い換えると、イベントはデリゲートに指定されているメソッドを呼び出します。
~

          

          
~
class Sample
ここではまずイベントを用いず、先ほどのサンプルをデリゲートだけを用いたサンプルに作り替えてみます。
~
{

          
~
  static void Main()
#code(vb,指定した時間だけウェイトする・デリゲート版){{
+
  {
+
    PrintTime();
+

          
+
    // PrintTimeメソッドのデリゲートを作成
+
    Action callback = new Action(PrintTime);
+

          
+
    // 作成したデリゲートを渡して待機
+
    Wait(TimeSpan.FromSeconds(3.0), callback);
+
  }
+

          
+
  static void PrintTime()
+
  {
+
    Console.WriteLine(DateTime.Now.ToString("T"));
+
  }
+

          
+
  static void Wait(TimeSpan timeout, Action callbackAction)
+
  {
+
    Thread.Sleep(timeout);
+

          
+
    // デリゲートが表すコールバックメソッドを呼び出す
+
    callbackAction();
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
 
Imports System
Imports System
 
Imports System.Threading
Imports System.Threading
 

        

        
~
Class Sample
Module Main
~
  Shared Sub Main()

          
~
    PrintTime()
    Public Delegate Sub WaitOverDelegate()
~

          

          
~
    ' PrintTimeメソッドのデリゲートを作成
    Dim WaitOverMethod As WaitOverDelegate
~
    Dim callback As New Action(AddressOf PrintTime)

          
~

          
    ' アプリケーションのエントリーポイント
~
    ' 作成したデリゲートを渡して待機
    Public Function Main(ByVal args() As String) As Integer
~
    Wait(TimeSpan.FromSeconds(3.0), callback)

          
~
  End Sub
        Console.WriteLine("Start: {0}", DateTime.Now)
~

          

          
~
  Shared Sub PrintTime()
        'WaitOverMethod = AddressOf WaitOver
~
    Console.WriteLine(DateTime.Now.ToString("T"))
        WaitOverMethod = New WaitOverDelegate(AddressOf WaitOver)
~
  End Sub

          
~

          
        Wait(3000)
~
  Shared Sub Wait(ByVal timeout As TimeSpan, ByVal callbackAction As Action)

          
~
    Thread.Sleep(timeout)
        Console.WriteLine("Finished: {0}", DateTime.Now)
~

          

          
~
    ' デリゲートが表すコールバックメソッドを呼び出す
        Return 0
~
    callbackAction()

          
~
  End Sub
    End Function
~
End Class

          
-
    ' 指定されたミリ秒だけウェイトする
-
    Public Sub Wait(ByVal millisec As Integer)
-

          
-
        Thread.Sleep(millisec)
-

          
-
        WaitOverMethod()
-

          
-
    End Sub
-

          
-
    ' ウェイトが終了したことを知らせる
-
    Public Sub WaitOver()
-

          
-
        Console.WriteLine("Wait over.")
-

          
-
    End Sub
-

          
-
End Module
 
}}
}}
+
#tabpage-end
 

        

        
~
#prompt{{
#prompt(実行結果){{
~
14:17:31
Start: 2003/02/21 0:00:39
~
14:17:34
Wait over.
-
Finished: 2003/02/21 0:00:42
-
Press any key to continue
 
}}
}}
 

        

        
~
実行結果のとおり、Waitメソッドにおいてデリゲートを通してPrintTimeメソッドが呼び出されていることが分かると思います。 このように、デリゲートにはある特定のメソッドを''参照''する役割を持っています。 デリゲートを使用する場合、メソッドを呼び出す側は直接特定のメソッドを呼び出す必要はなく、デリゲートが表すメソッドを間接的に呼び出すことが出来ます。
早速デリゲートが出てきましたが、落ち着いてみてみましょう。 このサンプルではWaitOverメソッドをデリゲートにより呼び出すことを試みています。 まず、6行目では新しいデリゲート型の宣言を行っています。 デリゲートではどんなメソッドでも呼び出せるわけではなく、引数・戻り値が一致するメソッドだけを呼び出せるので、そのデリゲートがどのようなメソッドを呼び出せるかを定義します。 ここでは、戻り値無し、引数無しのメソッドを WaitOverDelegateとしています。
 

        

        
~
言い換えると、デリゲートを用いることで任意のメソッドを一種の変数や引数のように扱うことが出来ます。 これにより、デリゲートを指定する側からすれば''呼び出させるメソッドを自由に指定''することができ、デリゲートを用いてメソッドを呼び出す側からすれば''呼び出しは行うが実際にどのようなメソッドが呼び出されるかは感知しない''ということになります。 delegateという動詞には「委譲する、委任する」、名詞では「代表、代理人」といった訳語が用いられることを知っておくとデリゲートの概念を大雑把に把握することができると思います。
続いて8行目はたった今宣言したデリゲート型のインスタンスを格納するために、WaitOverDelegate型のオブジェクト変数 WaitOverMethodを宣言しています。 WaitOverDelegate型とおなじ形式のメソッドを呼び出すためにはこの変数に呼び出したいメソッドを指定します。 実際に指定しているのが15・16行目です。 コメント化されている部分は、このコードでも意味は変わらないということを示しているので、どちらか好きな方でコーディングすることができます。 このコードのようにデリゲート型の変数に呼び出させたいメソッドを指定するには、「AddressOf (対象のメソッド)」という風に指定してやります。 ここで、対象のメソッドは、引数・戻り値がデリゲート型で指定されているものと一致する共有メソッドまたはアクセス権のあるインスタンスメソッドです。
 

        

        
~
上記の点をより明確にするために、先の例を書き換え他のコールバックメソッドも呼び出すように変えてみます。 Waitメソッドには変更を加えていない点に注目してください。
さて、デリゲート型の変数に呼び出させたいメソッドを指定することができたら、後は呼び出すだけです。 実際に呼び出しを行っているのが29行目です。 一見WaitOverMethod()というメソッドを呼び出しているように見えますが、実際にはデリゲート型変数 WaitOverMethod()に指定されているメソッドであるWaitOver()が呼び出されています。 ですから、直接WaitOver()メソッドを呼び出すようなコードは記述されていなくても、デリゲートが間接的に呼び出してくれるわけです。
 

        

        
~
#tabpage(C#)
このサンプルをさらに改変して、インスタンスのメソッドも呼び出せるかどうかを実際に見てみます。
~
#code(cs){{

          
~
using System;
*デリゲートでインスタンスメソッドを呼び出す
~
using System.Threading;
前のサンプルをクラス化して改変を加えたものが次のソースコードです。 ざっとコードの概要を説明しますと、今回使用しているデリゲート型WaitOverDelegateは、各クラス共通に用いられるのでPublicとし、クラス外で宣言します。
~

          

          
~
class Sample
さらに、機能別に三つのクラスに分割しました。 MainクラスはアプリケーションのエントリーポイントとしてのMainメソッドを提供し、プログラムの流れを記述します。 WaitExecuterクラスはウェイトを実行するためのクラスで、ウェイトが完了するとWaitOverMethodで指定されているメソッドを呼び出します。 WaitOverNotifierクラスは、ウェイトが終了したことを通知するためのクラスです。
~
{

          
~
  static void Main()
#code(vb,指定した時間だけウェイトする・クラス化版){{
+
  {
+
    PrintTime();
+

          
+
    // PrintTimeをコールバックするように指定して待機
+
    Wait(TimeSpan.FromSeconds(3.0), new Action(PrintTime));
+

          
+
    // PrintMessageをコールバックするように指定して待機
+
    Wait(TimeSpan.FromSeconds(3.0), new Action(PrintMessage));
+
  }
+

          
+
  static void PrintTime()
+
  {
+
    Console.WriteLine(DateTime.Now.ToString("T"));
+
  }
+

          
+
  static void PrintMessage()
+
  {
+
    Console.WriteLine("done");
+
  }
+

          
+
  static void Wait(TimeSpan timeout, Action callbackAction)
+
  {
+
    Thread.Sleep(timeout);
+

          
+
    // デリゲートが表すコールバックメソッドを呼び出す
+
    callbackAction();
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
 
Imports System
Imports System
 
Imports System.Threading
Imports System.Threading
 

        

        
~
Class Sample
' デリゲート型の宣言
~
  Shared Sub Main()
Public Delegate Sub WaitOverDelegate()
~
    PrintTime()

          
~

          
Public Class Main
~
    ' PrintTimeをコールバックするように指定して待機

          
~
    Wait(TimeSpan.FromSeconds(3.0), New Action(AddressOf PrintTime))
    ' アプリケーションのエントリーポイント
~

          
    Public Shared Function Main(ByVal args() As String) As Integer
~
    ' PrintMessageをコールバックするように指定して待機

          
~
    Wait(TimeSpan.FromSeconds(3.0), New Action(AddressOf PrintMessage))
        Dim e As New WaitExecuter()
~
  End Sub
        Dim n As New WaitOverNotifier()
~

          

          
~
  Shared Sub PrintTime()
        ' ウェイトが終了したことを通知するインスタンスメソッドを指定
~
    Console.WriteLine(DateTime.Now.ToString("T"))
        e.WaitOverMethod = AddressOf n.WaitOver
~
  End Sub

          
~

          
        Console.WriteLine("Start: {0}", DateTime.Now)
~
  Shared Sub PrintMessage()

          
~
    Console.WriteLine("done")
        e.Wait(3000)
+
  End Sub
+

          
+
  Shared Sub Wait(ByVal timeout As TimeSpan, ByVal callbackAction As Action)
+
    Thread.Sleep(timeout)
+

          
+
    ' デリゲートが表すコールバックメソッドを呼び出す
+
    callbackAction()
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
#prompt{{
        Console.WriteLine("Finished: {0}", DateTime.Now)
+
15:26:15
+
15:26:18
+
done
+
}}
 

        

        
~
ここまでの例では、引数なし・戻り値なし(void)のデリゲート&msdn(netfx,type,System.Action){Action};を使用していますが、引数や戻り値のあるメソッドもデリゲートに指定することが出来ます。 次の例では、T型の引数を一つとるジェネリックデリゲート&msdn(netfx,id,018hxwa8){Action<T>};を使ってコールバックを行っています。
        Return 0
 

        

        
~
#tabpage(C#)
    End Function
+
#code(cs){{
+
using System;
+
using System.Threading;
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    Print("start");
+

          
+
    // Printをコールバックするように指定して待機
+
    Wait(TimeSpan.FromSeconds(3.0), new Action<string>(Print));
+
  }
+

          
+
  static void Print(string message)
+
  {
+
    Console.WriteLine(message);
+
  }
+

          
+
  static void Wait(TimeSpan timeout, Action<string> callbackAction)
+
  {
+
    Thread.Sleep(timeout);
+

          
+
    // デリゲートが表すコールバックメソッドに引数を指定して呼び出す
+
    callbackAction("done");
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
+
Imports System.Threading
 

        

        
+
Class Sample
+
  Shared Sub Main()
+
    Print("start")
+

          
+
    ' Printをコールバックするように指定して待機
+
    Wait(TimeSpan.FromSeconds(3.0), New Action(Of String)(AddressOf Print))
+
  End Sub
+

          
+
  Shared Sub Print(ByVal message As String)
+
    Console.WriteLine(message)
+
  End Sub
+

          
+
  Shared Sub Wait(ByVal timeout As TimeSpan, ByVal callbackAction As Action(Of String))
+
    Thread.Sleep(timeout)
+

          
+
    ' デリゲートが表すコールバックメソッドに引数を指定して呼び出す
+
    callbackAction("done")
+
  End Sub
 
End Class
End Class
+
}}
+
#tabpage-end
 

        

        
~
#prompt{{
' ウェイトを行うためのクラス
~
start
Public Class WaitExecuter
~
done

          
~
}}
    Public WaitOverMethod As WaitOverDelegate
 

        

        
~
なお、いくつかのデリゲートを連結して一つのデリゲートを作成し、複数のメソッドを呼び出すようにすることも出来ます(マルチキャストデリゲート)。 詳しくは、[[programming/netfx/delegate/2_operations#multicasting]]で解説します。
    ' 指定されたミリ秒だけウェイトする
-
    Public Sub Wait(ByVal millisec As Integer)
 

        

        
~
*デリゲートとメソッド
        Thread.Sleep(millisec)
+
メソッドからデリゲートをするには、メソッドとデリゲートのシグネチャ(signature、引数と戻り値の型の組み合わせ)が一致している必要があります。 関数ポインタとは異なり、デリゲートはタイプセーフであり、シグネチャが一致していないメソッドからデリゲートを作成することは出来ません。
 

        

        
~
先の例で使用した&msdn(netfx,type,System.Action){Actionデリゲート};は引数なし・戻り値なし(void)のメソッドを表すデリゲートです。 これと異なるシグネチャのメソッドからはActionデリゲートを作成することは出来ません。
        ' ウェイト終了を通知
-
        WaitOverMethod()
 

        

        
~
#tabpage(C#)
    End Sub
+
#code(cs){{
+
using System;
+

          
+
class Sample
+
{
+
  // 引数なし、戻り値なしのメソッド
+
  static void Method1()
+
  {
+
  }
+

          
+
  // 引数にstring型を取り、戻り値なしのメソッド
+
  static void Method2(string arg)
+
  {
+
  }
+

          
+
  // 引数なし、int型の戻り値を持つメソッド
+
  static int Method3()
+
  {
+
    return 0;
+
  }
+

          
+
  static void Main()
+
  {
+
    Action a1 = new Action(Method1);
+
    // Action a2 = new Action(Method2); 戻り値が一致しないため、コンパイルエラーとなる
+
    // Action a3 = new Action(Method3); 引数が一致しないため、コンパイルエラーとなる
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
 

        

        
+
Class Sample
+
  ' 引数なし、戻り値なしのメソッド
+
  Shared Sub Method1()
+
  End Sub
+

          
+
  ' 引数にstring型を取り、戻り値なしのメソッド
+
  Shared Sub Method2(ByVal arg As String)
+
  End Sub
+

          
+
  ' 引数なし、int型の戻り値を持つメソッド
+
  Shared Function Method3() As Integer
+
    Return 0
+
  End Function
+

          
+
  Shared Sub Main()
+
    Dim a1 As New Action(AddressOf Method1)
+
    ' Dim a2 As New Action(AddressOf Method2) 戻り値が一致しないため、コンパイルエラーとなる
+
    ' Dim a3 As New Action(AddressOf Method3) 引数が一致しないため、コンパイルエラーとなる
+
  End Sub
 
End Class
End Class
+
}}
+
#tabpage-end
 

        

        
~
また、静的(共有)メソッドだけでなく、インスタンスメソッドを設定することも出来ます。 デリゲートは、メソッドを呼び出す際にインスタンスを区別して呼び出します。
' ウェイト終了を感知するためのクラス
-
Public Class WaitOverNotifier
 

        

        
~
#tabpage(C#)
    ' ウェイトが終了したことを知らせる
~
#code(cs){{
    Public Sub WaitOver()
+
using System;
+

          
+
class TestClass
+
{
+
  private string message;
+

          
+
  public TestClass(string message)
+
  {
+
    this.message = message;
+
  }
+

          
+
  // インスタンスメソッド
+
  public void Print()
+
  {
+
    Console.WriteLine(message);
+
  }
+

          
+
  // 静的メソッド
+
  public static void PrintMessage()
+
  {
+
    Console.WriteLine("Hello, world!");
+
  }
+
}
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    TestClass i1 = new TestClass("instance #1");
+
    TestClass i2 = new TestClass("instance #2");
+

          
+
    Action a1 = new Action(i1.Print); // インスタンスi1のPrintメソッド
+
    Action a2 = new Action(i2.Print); // インスタンスi2のPrintメソッド
+
    Action a3 = new Action(TestClass.PrintMessage); // TestClassのPrintメソッド
+

          
+
    a1();
+
    a2();
+
    a3();
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
 

        

        
~
Class TestClass
        Console.WriteLine("Wait over.")
+
  Private message As String
 

        

        
~
  Public Sub New(ByVal message As String)
    End Sub
+
    MyClass.message = message
+
  End Sub
+

          
+
  ' インスタンスメソッド
+
  Public Sub Print()
+
    Console.WriteLine(message)
+
  End Sub
+

          
+
  ' 共有メソッド
+
  Public Shared Sub PrintMessage()
+
    Console.WriteLine("Hello, world!")
+
  End Sub
+
End Class
 

        

        
+
Class Sample
+
  Shared Sub Main()
+
    Dim i1 As New TestClass("instance#1")
+
    Dim i2 As New TestClass("instance#2")
+

          
+
    Dim a1 As New Action(AddressOf i1.Print) ' インスタンスi1のPrintメソッド
+
    Dim a2 As New Action(AddressOf i2.Print) ' インスタンスi2のPrintメソッド
+
    Dim a3 As New Action(AddressOf TestClass.PrintMessage) ' TestClassのPrintメソッド
+

          
+
    a1()
+
    a2()
+
    a3()
+
  End Sub
 
End Class
End Class
 
}}
}}
+
#tabpage-end
 

        

        
~
#prompt{{
#prompt(実行結果){{
~
instance#1
Start: 2003/02/21 2:40:06
~
instance#2
Wait over.
~
Hello, world!
Finished: 2003/02/21 2:40:09
-
Press any key to continue
 
}}
}}
 

        

        
~
さらに、オーバーライドされたメソッドや、(デリゲートが作成出来る場合は)非パブリックなメソッドでも呼び出すことが出来ます。
見ての通り実行結果は予想通りで、インスタンスメソッドでもデリゲートはちゃんと機能します。 参考までに、デリゲートは異なるインスタンスのメソッドを区別します。 つまり、インスタンスAとインスタンスBのメソッドMは別のものとして扱われます。
 

        

        
~
#tabpage(C#)
次はイベントを定義することにします。
~
#code(cs){{

          
~
using System;
*イベント
~

          
さて、ここでやっとなじみ深いイベントが出てくるわけです。 イベントはクラス内の状態の変化などを外部に伝える役割を持っています。 ところで、デリゲートはメソッドを特定する役割を持つとしました。 イベントは外部に情報を伝えるためにありますが、伝えるための各々のメソッド、つまりイベントハンドラはどんなメソッドでもいいわけではなく、引数や戻り値がすべて一致している必要があります。 ここで、イベントハンドラの型を定義するためにデリゲート型を用います。
~
class BaseClass

          
~
{
説明だけではよくわからないと思うので、実際にイベントとイベントハンドラを用いたサンプルを次に示します。 例によって、前のサンプルを少し改変したものです。
~
  public virtual void Print()

          
~
  {
#code(vb,指定した時間だけウェイトする・イベント版){{
+
    Console.WriteLine("BaseClass.Print");
+
  }
+
}
+

          
+
class DerivedClass : BaseClass
+
{
+
  public override void Print()
+
  {
+
    Console.WriteLine("DerivedClass.Print");
+
  }
+

          
+
  private void PrintPrivate()
+
  {
+
    Console.WriteLine("DerivedClass.PrintPrivate");
+
  }
+

          
+
  public Action GetDelegate()
+
  {
+
    // プライベートなメソッドへのデリゲートを作成して返す
+
    return new Action(PrintPrivate);
+
  }
+
}
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    BaseClass b = new BaseClass();
+
    DerivedClass d = new DerivedClass();
+

          
+
    Action a1 = new Action(b.Print);
+
    Action a2 = new Action(d.Print);
+
    Action a3 = d.GetDelegate();
+

          
+
    a1();
+
    a2();
+
    a3();
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
 
Imports System
Imports System
-
Imports System.Threading
-

          
-
' デリゲート型の宣言
-
Public Delegate Sub WaitOverDelegate()
-

          
-
Public Class Main
-

          
-
    ' アプリケーションのエントリーポイント
-
    Public Shared Function Main(ByVal args() As String) As Integer
-

          
-
        Dim e As New WaitExecuter()
-
        Dim n As New WaitOverNotifier()
-

          
-
        ' ウェイトが終了したことを通知するイベントハンドラを指定
-
        AddHandler e.WaitOver, AddressOf n.WaitOver
-

          
-
        Console.WriteLine("Start: {0}", DateTime.Now)
-

          
-
        e.Wait(3000)
-

          
-
        Console.WriteLine("Finished: {0}", DateTime.Now)
-

          
-
        Return 0
-

          
-
    End Function
 

        

        
+
Class BaseClass
+
  Public Overridable Sub Print()
+
    Console.WriteLine("BaseClass.Print")
+
  End Sub
 
End Class
End Class
 

        

        
~
Class DerivedClass
' ウェイトを行うためのクラス
~
  Inherits BaseClass
Public Class WaitExecuter
-

          
-
    ' ウェイトが終了したことを通知するイベント
-
    Public Event WaitOver As WaitOverDelegate
-

          
-
    ' 指定されたミリ秒だけウェイトする
-
    Public Sub Wait(ByVal millisec As Integer)
-

          
-
        Thread.Sleep(millisec)
-

          
-
        ' ウェイト終了イベントを発生
-
        RaiseEvent WaitOver()
-

          
-
    End Sub
 

        

        
+
  Public Overrides Sub Print()
+
    Console.WriteLine("DerivedClass.Print")
+
  End Sub
+

          
+
  Private Sub PrintPrivate()
+
    Console.WriteLine("DerivedClass.PrintPrivate")
+
  End Sub
+

          
+
  Public Function GetDelegate() As Action
+
    ' プライベートなメソッドへのデリゲートを作成して返す
+
    Return New Action(AddressOf PrintPrivate)
+
  End Function
 
End Class
End Class
 

        

        
~
Class Sample
' ウェイト終了を感知するためのクラス
~
  Shared Sub Main()
Public Class WaitOverNotifier
~
    Dim b As New BaseClass()

          
~
    Dim d As New DerivedClass()
    ' ウェイトが終了したことを知らせる
~

          
    Public Sub WaitOver()
~
    Dim a1 As New Action(AddressOf b.Print)

          
~
    Dim a2 As New Action(AddressOf d.Print)
        Console.WriteLine("Wait over.")
~
    Dim a3 As Action = d.GetDelegate()

          
~

          
    End Sub
~
    a1()

          
+
    a2()
+
    a3()
+
  End Sub
 
End Class
End Class
 
}}
}}
+
#tabpage-end
 

        

        
~
#prompt{{
#prompt(実行結果){{
~
BaseClass.Print
Start: 2003/02/21 3:05:40
~
DerivedClass.Print
Wait over.
~
DerivedClass.PrintPrivate
Finished: 2003/02/21 3:05:43
-
Press any key to continue
 
}}
}}
 

        

        
~
なお、デリゲートの[[MethodプロパティやTargetプロパティ>programming/netfx/delegate/2_operations#invocationlist]]を参照することでデリゲートが呼び出すメソッドやインスタンスを知ることが出来ます。
まず、このコードで大きく変わったところから説明していきます。 まず、32行目ですが、以前はデリゲート型のパブリック変数が宣言されていましたが、ここではEventキーワードによりイベントとして宣言されています。 また、「Event (イベント名) As (デリゲート型)」とすることで、このイベントのハンドラはデリゲート型で指定されている形式と同じ形式を持たなければならなくなります。
-

          
-
イベント宣言へと変わったことで、16行目も変わりました。 AddHandlerステートメントは、イベントに適切なイベントハンドラを追加するためのものです。 参考までに、イベントのリストから削除するためにRemoveEventステートメントも用意されています。  AddHandlerステートメントは「AddHandler (イベント), AddressOf (イベントハンドラ)」のように記述します。 これにより16行目では、e.WaitOverイベントにn.WaitOver()をイベントハンドラとして追加することができます。
 

        

        
~
*デリゲートと構文
最後に、デリゲートの時はメソッド同様に呼び出すことができましたが、イベントの場合は異なります。 40行目にあるように、 RaiseEventステートメントでイベントを発生させます。 このとき、WaitOverに指定されている全てのイベントハンドラが呼び出されます。
+
メソッドからデリゲートのインスタンスを作成するには、デリゲート型のコンストラクタを使う他、言語によって用意されているいくつかの構文を使うことが出来ます。 C#ではキャストや単純な代入、VBでは&msdn(netfx,id,y72ewk2b){AddressOf演算子};を使うことでコンストラクタを呼び出さなくてもデリゲートを作成することが出来ます。
 

        

        
~
#code(cs,C#){{
この例ではイベントハンドラを一つしか設定しませんでしたが、一つのイベントに対して複数のイベントハンドラを設定することも可能です。 また、イベントも同様に複数定義できます。 この次では、イベントに引数を持たせることでクラス内の情報を詳細に伝えることを考えてみます。
+
using System;
 

        

        
~
class Sample
*イベントの引数
~
{
ただ単にイベントハンドラを呼び出しただけでは、そのイベントが発生したときのクラス内の状態を知ることができません。 そのため、イベントに引数を持たせることを考えてみます。
~
  static void Method()

          
~
  {
VB.NETのコントロールなどのほとんどのイベントハンドラでは、イベントを発生させたオブジェクトを知るための sender と、そのイベントが発生した時点での状況などを記した e が、イベントハンドラの引数として取得できます。 具体的には sender は Object型、e は EventArgsクラスを継承した型です。
~
    Console.WriteLine("Hello, world!");

          
~
  }
このサンプルでも、その慣例に従って、EventArgsクラスを継承したクラスWaitOverEventArgsをイベントの引数として渡してみることにします。 このクラスにはウェイトが完了した時刻を格納させるようにします。 また、イベントに引数を持たせるため、デリゲートも多少変更を加え、名前もWaitOverEventHandlerに変えます。
+

          
+
  static void Main()
+
  {
+
    Action a1 = new Action(Method);
+
    Action a2 = (Action)Method;
+
    Action a3 = Method;
+

          
+
    a1();
+
    a2();
+
    a3();
+
  }
+
}
+
}}
 

        

        
~
#code(vb,VB){{
#code(vb,指定した時間だけウェイトする・引数付きイベント版)){{
 
Imports System
Imports System
-
Imports System.Threading
 

        

        
~
Class Sample
' デリゲート型の宣言
~
  Shared Sub Method()
Public Delegate Sub WaitOverEventHandler(ByVal sender As Object, ByVal e As WaitOverEventArgs)
~
    Console.WriteLine("Hello, world!")

          
~
  End Sub
Public Class Main
+

          
+
  Shared Sub Main()
+
    Dim a1 As New Action(AddressOf Method)
+
    Dim a2 As Action = AddressOf Method
+

          
+
    a1()
+
    a2()
+
  End Sub
+
End Class
+
}}
 

        

        
~
また、匿名メソッド(&msdn(netfx,id,0yw3tz5k){C# 2.0};)、ラムダ式(&msdn(netfx,id,bb397687){C# 3.0};、&msdn(netfx,id,bb531253){VB10};)の構文を使う事によってもデリゲートを作成することが出来ます。 ここでは個々の言語の構文についての解説は省略します。
    ' アプリケーションのエントリーポイント
-
    Public Shared Function Main(ByVal args() As String) As Integer
 

        

        
~
#code(cs,C# 2.0){{
        Dim e As New WaitExecuter()
~
using System;
        Dim n As New WaitOverNotifier()
 

        

        
~
class Sample
        ' ウェイトが終了したことを通知するイベントハンドラを指定
~
{
        AddHandler e.WaitOver, AddressOf n.WaitOver
+
  static void Main()
+
  {
+
    // 匿名メソッド
+
    Action a = delegate { Console.WriteLine("Hello, world!"); };
+

          
+
    a();
+
  }
+
}
+
}}
 

        

        
~
#code(cs,C# 3.0){{
        Console.WriteLine("Start: {0}", DateTime.Now)
+
using System;
 

        

        
~
class Sample
        e.Wait(3000)
+
{
+
  static void Main()
+
  {
+
    // ラムダ式
+
    Action a = () => Console.WriteLine("Hello, world!");
+

          
+
    a();
+
  }
+
}
+
}}
 

        

        
~
#code(vb,VB10){{
        Return 0
+
Imports System
 

        

        
~
Class Sample
    End Function
+
  Shared Sub Main()
+
    ' ラムダ式
+
    Dim a As Action = Sub()
+
      Console.WriteLine("Hello, world!")
+
    End Sub
 

        

        
+
    a()
+
  End Sub
 
End Class
End Class
+
}}
 

        

        
+
*デリゲートの宣言
+
.NET Frameworkでは多くのデリゲート型が用意されていますが、独自に宣言して使用することも出来ます。 C#では&msdn(netfx,id,900fyy8e){delegateキーワード};、VBでは&msdn(netfx,id,twsk0311){Delegateステートメント};を使って宣言します。 いずれも実体を持たないメソッドの宣言のような記述になっています。
 

        

        
~
#tabpage(C#)
' ウェイトが終了したときのイベント引数のためのクラス
~
#code(cs){{
Public Class WaitOverEventArgs
~
using System;

          
~

          
    ' EventArgsを継承
~
// 引数なし、戻り値なしのメソッドを表すデリゲート
    Inherits EventArgs
~
public delegate void MyDelegate1();

          
~

          
    ' ウェイトが終了したときの時間
~
// int型の引数を一つ取り、戻り値なしのメソッドを表すデリゲート
    Protected m_dtm As DateTime
+
internal delegate void MyDelegate2(int arg);
+

          
+
// string型の引数を一つ取り、int型の戻り値を返すメソッドを表すデリゲート
+
delegate int MyDelegate3(string arg);
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    MyDelegate1 m1 = new MyDelegate1(PrintMessage);
+
    MyDelegate2 m2 = new MyDelegate2(PrintMessage);
+
    MyDelegate3 m3 = new MyDelegate3(GetLength);
+

          
+
    m1();
+

          
+
    m2(6);
+

          
+
    Console.WriteLine(m3("Hello, world!"));
+
  }
+

          
+
  static void PrintMessage()
+
  {
+
    Console.WriteLine("Hello, world!");
+
  }
+

          
+
  static void PrintMessage(int count)
+
  {
+
    for (int i = 0; i < count; i++)
+
    {
+
      Console.Write("Hello! ");
+
    }
+

          
+
    Console.WriteLine();
+
  }
+

          
+
  static int GetLength(string str)
+
  {
+
    return str.Length;
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
 

        

        
~
' 引数なし、戻り値なしのメソッドを表すデリゲート
    ' 読み取り専用プロパティ
~
Public Delegate Sub MyDelegate1()
    Public ReadOnly Property WaitOverTime() As DateTime
-
        Get
-
            Return m_dtm
-
        End Get
-
    End Property
 

        

        
~
' Integer型の引数を一つ取り、戻り値なしのメソッドを表すデリゲート
    ' コンストラクタ
~
Friend Delegate Sub MyDelegate2(ByVal arg As Integer)
    Public Sub New(ByVal dtm As DateTime)
-
        MyBase.New()
-
        m_dtm = dtm
-
    End Sub
 

        

        
~
' String型の引数を一つ取り、Integer型の戻り値を返すメソッドを表すデリゲート
End Class
+
Delegate Function MyDelegate3(ByVal arg As String) As Integer
 

        

        
+
Class Sample
+
  Shared Sub Main()
+
    Dim m1 As New MyDelegate1(AddressOf PrintMessage)
+
    Dim m2 As New MyDelegate2(AddressOf PrintMessage)
+
    Dim m3 As New MyDelegate3(AddressOf GetLength)
 

        

        
~
    m1()
' ウェイトを行うためのクラス
-
Public Class WaitExecuter
 

        

        
~
    m2(6)
    ' ウェイトが終了したことを通知するイベント
-
    Public Event WaitOver As WaitOverEventHandler
 

        

        
~
    Console.WriteLine(m3("Hello, world!"))
    ' 指定されたミリ秒だけウェイトする
~
  End Sub
    Public Sub Wait(ByVal millisec As Integer)
 

        

        
~
  Shared Sub PrintMessage()
        Thread.Sleep(millisec)
+
    Console.WriteLine("Hello, world!")
+
  End Sub
 

        

        
~
  Shared Sub PrintMessage(ByVal count As Integer)
        ' ウェイト終了イベントを発生
~
    For i As Integer = 0 To count - 1
        RaiseEvent WaitOver(Me, New WaitOverEventArgs(DateTime.Now))
+
      Console.Write("Hello! ")
+
    Next
 

        

        
~
    Console.WriteLine()
    End Sub
+
  End Sub
 

        

        
+
  Shared Function GetLength(ByVal str As String) As Integer
+
    Return str.Length
+
  End Function
 
End Class
End Class
+
}}
+
#tabpage-end
 

        

        
+
#prompt{{
+
Hello, world!
+
Hello! Hello! Hello! Hello! Hello! Hello! 
+
13
+
}}
 

        

        
~
なお、デリゲート型に対するアクセス修飾子はあくまで型に対してのもので、デリゲートが表すメソッドのアクセス修飾子には関係しません。
' ウェイト終了を感知するためのクラス
-
Public Class WaitOverNotifier
-

          
-
    ' ウェイトが終了したことを知らせる
-
    Public Sub WaitOver(ByVal sender As Object, ByVal e As WaitOverEventArgs)
 

        

        
~
**汎用的なデリゲート
        ' ウェイト終了時間を出力する
~
多くの場合、独自にデリゲート型を宣言するよりも、.NET Frameworkで用意されているデリゲート型を使うことになります。 以下は、よく使われるデリゲート・汎用的なデリゲートの一例です。
        Console.WriteLine("Wait over, {0}.", e.WaitOverTime)
 

        

        
~
|*.NET Frameworkで用意されているデリゲート型
    End Sub
+
|~デリゲート型|~名前空間|~主な用途|h
+
|&msdn(netfx,type,System.AsyncCallback){AsyncCallback};|System|[[非同期呼び出し>programming/netfx/delegate/2_operations#asynccall]]が完了した場合に呼び出されるコールバックメソッドを指定するために用いられる。|
+
|&msdn(netfx,id,tfakywbh){Comparison<T>};|System|コレクション型のクラスで二つのT型の値の大小関係を比較するメソッドを指定するために用いられる。|
+
|&msdn(netfx,id,kt456a2y){Converter<TInput, TOutput>};|System|コレクション型のクラスでTInput型の値をTOutput型に変換するメソッドを指定するために用いられる。|
+
|&msdn(netfx,id,bfcke1bz){Predicate<T>};|System|コレクション型のクラスでT型の値に対して真偽値を返す'''述語'''となるメソッドを指定するために用いられる。|
+
|&msdn(netfx,type,System.EventHandler){EventHandler};&br;&msdn(netfx,id,db0etb8x){EventHandler<TEventArgs>};|System|各種イベントのハンドラとなるメソッドを指定するために用いられる。|
+
|&msdn(netfx,type,System.Action){Action};&br;&msdn(netfx,id,018hxwa8){Action<T>};&br;&msdn(netfx,id,bb549311){Action<T1, T2>};&br;&msdn(netfx,id,bb549392){Action<T1, T2, T3>};など|System|戻り値のないメソッドを表すデリゲート。 渡された引数に応じて任意の処理を行うメソッドを指定するために用いられる。|
+
|&msdn(netfx,id,bb534960){Func<TResult>};&br;&msdn(netfx,id,bb549151){Func<T, TResult>};&br;&msdn(netfx,id,bb534647){Func<T1, T2, TResult>};&br;&msdn(netfx,id,bb549430){Func<T1, T2, T3, TResult>};など|System|TResult型の戻り値を返すメソッドを表すデリゲート。 渡された引数に応じて任意の処理を行った結果を返すメソッドを指定するために用いられる。|
+
|&msdn(netfx,type,System.Threading.ThreadStart){ThreadStart};&br;&msdn(netfx,type,System.Threading.ParameterizedThreadStart){ParameterizedThreadStart};|System.Threading|&msdn(netfx,type,System.Threading.Thread){Threadクラス};でスレッドを作成する場合に、スレッドとして起動するメソッドを指定するために用いられる。|
+
|&msdn(netfx,type,System.Threading.WaitCallback){WaitCallback};|System.Threading|&msdn(netfx,type,System.Threading.ThreadPool){ThreadPoolクラス};でスレッドプールを使って別スレッドで動作させるメソッドを指定するために用いられる。|
+
|&msdn(netfx,type,System.Net.Security.RemoteCertificateValidationCallback){RemoteCertificateValidationCallback};|System.Net.Security|&msdn(netfx,type,System.Net.Security.SslStream){SslStreamクラス};でリモートサーバから受信したSSL証明書の妥当性を検証するメソッドを指定するために用いられる。|
+
|&msdn(netfx,type,System.Text.RegularExpressions.MatchEvaluator){MatchEvaluator};|System.Text.RegularExpressions|&msdn(netfx,type,System.Text.RegularExpressions.Regex){Regexクラス};で正規表現にマッチする箇所が見つかる度にコールバックされ、別の文字列に置き換えるメソッドを指定するために用いられる。|
+
|&msdn(netfx,type,System.Xml.Schema.ValidationEventHandler){ValidationEventHandler};|System.Xml.Schema|&msdn(netfx,type,System.Xml.XmlReader){XmlReaderクラス};や&msdn(netfx,type,System.Xml.XmlValidatingReader){XmlValidatingReaderクラス};でXMLスキーマによるXML文書の検証でエラーや警告が発生した場合にコールバックされるメソッドを指定するために用いられる。|
+

          
+
以下はList<T>クラスといくつかのデリゲートを使った例です。 この例で使用しているメソッドについては[[List<T>についての解説>programming/netfx/collections/2_generic#List]]を参照してください。
+

          
+
#tabpage(C#)
+
#code(cs){{
+
using System;
+
using System.Collections.Generic;
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    List<string> list = new List<string>() {
+
      "Alice",
+
      "Bob",
+
      "Charlie",
+
      "Dave",
+
      "Eve",
+
    };
+

          
+
    // Comparison<string>を指定してソート
+
    list.Sort(CompareByLength);
+

          
+
    // Converter<string, string>を指定して変換
+
    List<string> converted = list.ConvertAll(PrependLength);
+

          
+
    // Action<string>を指定して列挙
+
    converted.ForEach(Print);
+
  }
+

          
+
  static int CompareByLength(string x, string y)
+
  {
+
    return x.Length - y.Length;
+
  }
+

          
+
  static string PrependLength(string str)
+
  {
+
    return string.Concat(str.Length.ToString(), ":", str);
+
  }
+

          
+
  static void Print(string str)
+
  {
+
    Console.WriteLine(str);
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
+
Imports System.Collections.Generic
 

        

        
+
Class Sample
+
  Shared Sub Main()
+
    Dim list As New List(Of String)(New String() { _
+
      "Alice", _
+
      "Bob", _
+
      "Charlie", _
+
      "Dave", _
+
      "Eve" _
+
    })
+

          
+
    ' Comparison(Of String)を指定してソート
+
    list.Sort(AddressOf CompareByLength)
+

          
+
    ' Converter(Of String, String)を指定して変換
+
    Dim converted As List(Of String) = list.ConvertAll(AddressOf PrependLength)
+

          
+
    ' Action(Of String)を指定して列挙
+
    converted.ForEach(AddressOf Print)
+
  End Sub
+

          
+
  Shared Function CompareByLength(ByVal x As String, ByVal y As String) As Integer
+
    Return x.Length - y.Length
+
  End Function
+

          
+
  Shared Function PrependLength(ByVal str As String) As String
+
    Return String.Concat(str.Length.ToString(), ":", str)
+
  End Function
+

          
+
  Shared Sub Print(ByVal str As String)
+
    Console.WriteLine(str)
+
  End Sub
 
End Class
End Class
 
}}
}}
+
#tabpage-end
 

        

        
~
#prompt{{
#prompt(実行結果){{
~
3:Eve
Start: 2003/02/21 3:51:39
~
3:Bob
Wait over, 2003/02/21 3:51:42.
~
4:Dave
Press any key to continue
+
5:Alice
+
7:Charlie
 
}}
}}
 

        

        
~
#navi(..)
まず、イベント引数のためのクラスWaitOverEventArgsが新しく定義されました。 このクラスはイベントが発生した時間を保持する以外の役割はありません。 64行目のRaiseEventステートメントでは、WaitOverイベントのデリゲート型が引数をとるように変わったので、それに伴い二つの引数を渡しています。 ここで、新しく作成されたWaitOverEventArgsを渡すことで、イベントが発生した時刻を知らせるわけです。 また、senderにMeを渡すことで、イベントを発生させた張本人である自分自身をイベントハンドラに通知します。
-

          
-
さらに、イベントハンドラの方も変更を加えます。 75行目のWaitOverイベントハンドラも、デリゲート型のパラメータ形式が変わったのでそれに伴い形式を変更します。 ここで、このイベントハンドラが呼び出されたとき、引数として渡されるeのWaitOverTimeプロパティにはこのイベントが発生した時点での時刻が格納されているので、これを表示することでウェイト終了時刻を表示してやります。
-

          
-
このように、ほとんど同じ結果が得られるにも関わらず、イベントを使う前の一番最初のコードと、イベントを定義した最後のコードでは、全く違うプログラムかのように見えるくらい両者は異なったものになっています。 しかし、デリゲートやイベントはクラスの状態変化を通知したりするのに非常に強力なものになるので、ぜひマスターするといいでしょう。
 

        

        

programming/netfx/delegate/1_events/index.wiki.txt

current previous
1,738 1,391
~
${smdncms:title,イベント}
${smdncms:title,イベントとデリゲート}
~
${smdncms:keywords,Event,EventHandler,EventArgs,AddHandler,RemoveHandler,RaiseEvent}
${smdncms:keywords,イベント,デリゲート,Delegate,Event,EventHandler,EventArgs,AddHandler}
 
${smdncms:meta,toc-amazonlivelink-keyword,books-jp,.net framework}
${smdncms:meta,toc-amazonlivelink-keyword,books-jp,.net framework}
-
Visual Basicではコマンドボタンがクリックされたときとか、テキストボックスに文字が入力されたときなどにイベントとしてメッセージが通知されてきました。 Visual Basic .NETでも同様のイベントというものが存在します。 また、Visual Basicでのクラス同様、独自のイベントを定義することができます。
 

        

        
~
#navi(..)
しかしVisual Basic .NETになってからイベントにまつわる部分が大きく変わりました。 個人的には.NETのイベントの方がわかりやすくなったかと思います。 ここではこのイベントと、それと切っても切り離せない関係にあるデリゲートについて考えてみたいと思います。
 

        

        
~
旧来のVBではイベントは言語の機能として提供されていましたが、.NET Frameworkにおいてはデリゲートの機能を使ってイベントが実現されます。 イベントを検知・受信する側は、デリゲートを使ってイベントハンドラとなるメソッドを指定し、イベントを発生・通知させる側は指定されたデリゲートを使ってイベントハンドラを呼び出します。 これによってイベントの発生と受信の機構が実現されます。
#googleadunit
 

        

        
~
----
*導入
-
まず、イベント・デリゲートをいきなり使う前に、これからイベントとデリゲートの説明に使うサンプルコードを提示します。
 

        

        
~
*イベントとデリゲート
#code(vb,指定した時間だけウェイトする){{
+
デリゲートだけを使ってイベント機構を実装することも出来ますが、C#では&msdn(netfx,id,8627sbea){eventキーワード};、VBでは&msdn(netfx,id,6hwhs172){Eventステートメント};を使うことでイベント機構をより簡単に実装できるようになっています。 以下は、クラスにイベントを実装した簡単な例です。
+

          
+
#tabpage(C#)
+
#code(cs){{
+
using System;
+
using System.Threading;
+

          
+
class WaitAndNotify
+
{
+
  // 待機を開始したときに発生させるイベント
+
  public event EventHandler WaitStart;
+

          
+
  // 待機が完了したときに発生させるイベント
+
  public event EventHandler WaitDone;
+

          
+
  public void Wait(TimeSpan timeout)
+
  {
+
    // 開始したらWaitStartイベントを発生させる
+
    if (WaitStart != null) WaitStart(this, EventArgs.Empty);
+

          
+
    // 指定された時間だけ待機
+
    Thread.Sleep(timeout);
+

          
+
    // 完了したらWaitDoneイベントを発生させる
+
    if (WaitDone != null) WaitDone(this, EventArgs.Empty);
+
  }
+
}
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    WaitAndNotify w = new WaitAndNotify();
+

          
+
    // WaitStart, WaitDoneイベントのハンドラを設定
+
    w.WaitStart += new EventHandler(WaitStartHandler);
+
    w.WaitDone  += new EventHandler(WaitDoneHandler);
+

          
+
    // Waitメソッドを呼び出して待機を開始する
+
    w.Wait(TimeSpan.FromSeconds(3.0));
+
  }
+

          
+
  static void WaitStartHandler(object sender, EventArgs e)
+
  {
+
    Console.WriteLine("{0:T}: start", DateTime.Now);
+
  }
+

          
+
  static void WaitDoneHandler(object sender, EventArgs e)
+
  {
+
    Console.WriteLine("{0:T}: done", DateTime.Now);
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
 
Imports System
Imports System
 
Imports System.Threading
Imports System.Threading
 

        

        
~
Class WaitAndNotify
Module Main
~
  ' 待機を開始したときに発生させるイベント

          
~
  Public Event WaitStart As EventHandler
    ' アプリケーションのエントリーポイント
~

          
    Public Function Main(ByVal args() As String) As Integer
~
  ' 待機が完了したときに発生させるイベント

          
~
  Public Event WaitDone As EventHandler
        Console.WriteLine("Start: {0}", DateTime.Now)
~

          

          
~
  Public Sub Wait(ByVal timeout As TimeSpan)
        Wait(3000)
~
    ' 開始したらWaitStartイベントを発生させる

          
~
    RaiseEvent WaitStart(Me, EventArgs.Empty)
        Console.WriteLine("Finished: {0}", DateTime.Now)
~

          

          
~
    ' 指定された時間だけ待機
        Return 0
~
    Thread.Sleep(timeout)

          
~

          
    End Function
~
    ' 完了したらWaitDoneイベントを発生させる

          
~
    RaiseEvent WaitDone(Me, EventArgs.Empty)
    ' 指定されたミリ秒だけウェイトする
~
  End Sub
    Public Sub Wait(ByVal millisec As Integer)
~
End Class

          
-
        Thread.Sleep(millisec)
-

          
-
        WaitOver()
-

          
-
    End Sub
-

          
-
    ' ウェイトが終了したことを知らせる
-
    Public Sub WaitOver()
-

          
-
        Console.WriteLine("Wait over.")
-

          
-
    End Sub
 

        

        
~
Class Sample
End Module
+
  Shared Sub Main()
+
    Dim w As New WaitAndNotify()
+

          
+
    ' WaitStart, WaitDoneイベントのハンドラを設定
+
    AddHandler w.WaitStart, AddressOf WaitStartHandler
+
    AddHandler w.WaitDone, AddressOf WaitDoneHandler
+

          
+
    ' Waitメソッドを呼び出して待機を開始する
+
    w.Wait(TimeSpan.FromSeconds(3.0))
+
  End Sub
+

          
+
  Shared Sub WaitStartHandler(ByVal sender As Object, ByVal e As EventArgs)
+
    Console.WriteLine("{0:T}: start", DateTime.Now)
+
  End SUb
+

          
+
  Shared Sub WaitDoneHandler(ByVal sender As Object, ByVal e As EventArgs)
+
    Console.WriteLine("{0:T}: done", DateTime.Now)
+
  End SUb
+
End Class
 
}}
}}
+
#tabpage-end
 

        

        
~
#prompt{{
#prompt(実行結果){{
~
13:59:34: start
Start: 2003/02/21 0:00:39
~
13:59:37: done
Wait over.
-
Finished: 2003/02/21 0:00:42
-
Press any key to continue
 
}}
}}
 

        

        
~
まずは、イベントを発生・通知する側から見ていきます。 この例のWaitAndNotifyクラスは待機を行う機能と、待機の開始と完了の時点でイベントを発生させて通知を行う機能を持っています。 開始と完了のイベントは、WaitStartイベントおよびWaitDoneイベントとして外部に公開しています。 この例では、イベントの型として&msdn(netfx,type,System.EventHandler){EventHandlerデリゲート};を使っています。 このデリゲートはイベントを発生させたインスタンスを表すobject型の引数と、発生したイベントの内容を表すEventArgs型の引数をとるメソッドを表します。 イベントを受信する側は、このシグネチャと一致するメソッドをイベントハンドラとして指定することで、WaitStartとWaitDoneのイベントの発生を検知することが出来るようになります。
ソースコードと実行結果とを見比べてもらえばすぐにわかりますが、複雑なことは何もやっていません。 63行目のThread.Sleep()は指定されたミリ秒だけ現在のスレッドを停止するというメソッドです。 Main()メソッドではWait()メソッドを呼び出す前後に、現在の時刻を表示するようにしています。 また、このサンプルでは3000ミリ秒、つまり3秒の間ウェイトするようにしています。 実行結果を見ればわかるように、始まりと終わりの間がちゃんと3秒になっています。
-

          
-
つぎに、このソースコードをデリゲートを使ったものに書き換えることにします。
 

        

        
~
実際にイベントを発生させているのがWaitAndNotify.Waitメソッドの部分です。 C#ではメソッド呼び出しと同様に、VBでは&msdn(netfx,id,fwd3bwed){RaiseEventステートメント};を使ってイベントに割り当てられているハンドラを呼び出しています。 EventHandlerデリゲートのシグネチャに合わせて、引数に現在のインスタンス(this, Me)と空のイベントを表す&msdn(netfx,member,System.EventArgs.Empty){EventArgs.Empty};を指定して呼び出しています。 ここで指定した引数は、イベントハンドラとなるメソッドに引き渡されることになります。
*デリゲート
-
まず最初にデリゲートについて考えてみることにします。 まず、この文章を読んで勉強しようと思われる方の多くは、「イベントは知っているけどデリゲートなんて聞いたことがない」というような方だと思います。 当初僕自身も、デリゲートが理解不能でした。 C++を勉強した方の中には「関数ポインタ」というものをご存じの方もいると思いますが、デリゲートの役割はこの関数ポインタに似ているものといえます(厳密には異なるものです)。
 

        

        
~
続いて、イベントを検知・受信する側について見ていきます。 C#では+=演算子、VBでは&msdn(netfx,id,7taxzxka){AddHandlerステートメント};を使うことで、WaitStartイベントとWaitDoneイベントにそれぞれのイベントハンドラとなるメソッドのデリゲートを指定しています。 イベントが発生すると、これらのメソッドが呼び出されることになります。 イベントが発生した際に呼び出されるメソッドがWaitStartHandlerとWaitDoneHandlerです。
というのも、デリゲートはあるメソッド(共有メソッド及びインスタンスメソッド)を特定する役割を持っているためです。 つまりその役割は、直接特定のメソッドを呼び出すのではなく、デリゲートによってそのデリゲートに指定されているメソッドを間接的に呼び出すことができるわけです。 イベントを発生するときに呼び出すべきメソッドをするためにデリゲートが使われます。 言い換えると、イベントはデリゲートに指定されているメソッドを呼び出します。
 

        

        
~
このようにしてイベントの発生と受信の機構を実装することが出来ます。 イベントとデリゲートの関係については、以下のドキュメントでより詳しく解説されています。
ここではまずイベントを用いず、先ほどのサンプルをデリゲートだけを用いたサンプルに作り替えてみます。
 

        

        
~
-&msdn(netfx,id,edzehd2t){イベントの処理と発生};
#code(vb,指定した時間だけウェイトする・デリゲート版){{
+
--&msdn(netfx,id,2ccyd347){イベントの利用};
+
--&msdn(netfx,id,wkzf914z){イベントの発生};
+

          
+
なお、上記の例では.NET Frameworkのガイドラインに従いイベントのデリゲートにEventHandlerを使用していますが、独自に宣言したデリゲートなどEventHandler以外のデリゲートを使ってもイベントを実装することも出来ます。 イベントを発生・通知させる側と検知・受信する側で同じシグネチャのデリゲートが使われていれば良く、引数の数や型はイベントとして通知したい内容に合わせて自由に選択することが出来ます。 以下は、上記の例をActionデリゲートを使って書き換えたものです。
+

          
+
#tabpage(C#)
+
#code(cs){{
+
using System;
+
using System.Threading;
+

          
+
class WaitAndNotify
+
{
+
  // 待機を開始したときに発生させるイベント
+
  public event Action WaitStart;
+

          
+
  // 待機が完了したときに発生させるイベント
+
  public event Action WaitDone;
+

          
+
  public void Wait(TimeSpan timeout)
+
  {
+
    // 開始したらWaitStartイベントを発生させる
+
    if (WaitStart != null) WaitStart();
+

          
+
    // 指定された時間だけ待機
+
    Thread.Sleep(timeout);
+

          
+
    // 完了したらWaitDoneイベントを発生させる
+
    if (WaitDone != null) WaitDone();
+
  }
+
}
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    WaitAndNotify w = new WaitAndNotify();
+

          
+
    // WaitStart, WaitDoneイベントのハンドラを設定
+
    w.WaitStart += new Action(WaitStartHandler);
+
    w.WaitDone  += new Action(WaitDoneHandler);
+

          
+
    // Waitメソッドを呼び出して待機を開始する
+
    w.Wait(TimeSpan.FromSeconds(3.0));
+
  }
+

          
+
  static void WaitStartHandler()
+
  {
+
    Console.WriteLine("{0:T}: start", DateTime.Now);
+
  }
+

          
+
  static void WaitDoneHandler()
+
  {
+
    Console.WriteLine("{0:T}: done", DateTime.Now);
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
 
Imports System
Imports System
 
Imports System.Threading
Imports System.Threading
 

        

        
~
Class WaitAndNotify
Module Main
~
  ' 待機を開始したときに発生させるイベント

          
~
  Public Event WaitStart As Action
    Public Delegate Sub WaitOverDelegate()
~

          

          
~
  ' 待機が完了したときに発生させるイベント
    Dim WaitOverMethod As WaitOverDelegate
~
  Public Event WaitDone As Action

          
~

          
    ' アプリケーションのエントリーポイント
~
  Public Sub Wait(ByVal timeout As TimeSpan)
    Public Function Main(ByVal args() As String) As Integer
~
    ' 開始したらWaitStartイベントを発生させる

          
~
    RaiseEvent WaitStart()
        Console.WriteLine("Start: {0}", DateTime.Now)
~

          

          
~
    ' 指定された時間だけ待機
        'WaitOverMethod = AddressOf WaitOver
~
    Thread.Sleep(timeout)
        WaitOverMethod = New WaitOverDelegate(AddressOf WaitOver)
~

          

          
~
    ' 完了したらWaitDoneイベントを発生させる
        Wait(3000)
~
    RaiseEvent WaitDone()

          
~
  End Sub
        Console.WriteLine("Finished: {0}", DateTime.Now)
~
End Class

          
-
        Return 0
-

          
-
    End Function
-

          
-
    ' 指定されたミリ秒だけウェイトする
-
    Public Sub Wait(ByVal millisec As Integer)
-

          
-
        Thread.Sleep(millisec)
-

          
-
        WaitOverMethod()
-

          
-
    End Sub
-

          
-
    ' ウェイトが終了したことを知らせる
-
    Public Sub WaitOver()
-

          
-
        Console.WriteLine("Wait over.")
-

          
-
    End Sub
-

          
-
End Module
-
}}
 

        

        
~
Class Sample
#prompt(実行結果){{
~
  Shared Sub Main()
Start: 2003/02/21 0:00:39
~
    Dim w As New WaitAndNotify()
Wait over.
~

          
Finished: 2003/02/21 0:00:42
~
    ' WaitStart, WaitDoneイベントのハンドラを設定
Press any key to continue
+
    AddHandler w.WaitStart, AddressOf WaitStartHandler
+
    AddHandler w.WaitDone, AddressOf WaitDoneHandler
+

          
+
    ' Waitメソッドを呼び出して待機を開始する
+
    w.Wait(TimeSpan.FromSeconds(3.0))
+
  End Sub
+

          
+
  Shared Sub WaitStartHandler()
+
    Console.WriteLine("{0:T}: start", DateTime.Now)
+
  End SUb
+

          
+
  Shared Sub WaitDoneHandler()
+
    Console.WriteLine("{0:T}: done", DateTime.Now)
+
  End SUb
+
End Class
 
}}
}}
+
#tabpage-end
 

        

        
~
*イベント引数
早速デリゲートが出てきましたが、落ち着いてみてみましょう。 このサンプルではWaitOverメソッドをデリゲートにより呼び出すことを試みています。 まず、6行目では新しいデリゲート型の宣言を行っています。 デリゲートではどんなメソッドでも呼び出せるわけではなく、引数・戻り値が一致するメソッドだけを呼び出せるので、そのデリゲートがどのようなメソッドを呼び出せるかを定義します。 ここでは、戻り値無し、引数無しのメソッドを WaitOverDelegateとしています。
~
.NET Frameworkのガイドラインに従ったイベントの場合、二つ目の引数に&netfx(netfx,type,System.EventArgs){EventArgsクラス};から派生した任意のクラスをイベント引数として指定します。 イベントが発生したときの状況や、追加の情報などはイベント引数としてイベントを受信する側に伝える事が出来ます。 先の例を少し変えて、イベント引数を使ってイベントが発生した時刻を取得できるようにしてみます。

          
-
続いて8行目はたった今宣言したデリゲート型のインスタンスを格納するために、WaitOverDelegate型のオブジェクト変数 WaitOverMethodを宣言しています。 WaitOverDelegate型とおなじ形式のメソッドを呼び出すためにはこの変数に呼び出したいメソッドを指定します。 実際に指定しているのが15・16行目です。 コメント化されている部分は、このコードでも意味は変わらないということを示しているので、どちらか好きな方でコーディングすることができます。 このコードのようにデリゲート型の変数に呼び出させたいメソッドを指定するには、「AddressOf (対象のメソッド)」という風に指定してやります。 ここで、対象のメソッドは、引数・戻り値がデリゲート型で指定されているものと一致する共有メソッドまたはアクセス権のあるインスタンスメソッドです。
-

          
-
さて、デリゲート型の変数に呼び出させたいメソッドを指定することができたら、後は呼び出すだけです。 実際に呼び出しを行っているのが29行目です。 一見WaitOverMethod()というメソッドを呼び出しているように見えますが、実際にはデリゲート型変数 WaitOverMethod()に指定されているメソッドであるWaitOver()が呼び出されています。 ですから、直接WaitOver()メソッドを呼び出すようなコードは記述されていなくても、デリゲートが間接的に呼び出してくれるわけです。
-

          
-
このサンプルをさらに改変して、インスタンスのメソッドも呼び出せるかどうかを実際に見てみます。
 

        

        
~
#tabpage(C#)
*デリゲートでインスタンスメソッドを呼び出す
~
#code(cs){{
前のサンプルをクラス化して改変を加えたものが次のソースコードです。 ざっとコードの概要を説明しますと、今回使用しているデリゲート型WaitOverDelegateは、各クラス共通に用いられるのでPublicとし、クラス外で宣言します。
~
using System;

          
~
using System.Threading;
さらに、機能別に三つのクラスに分割しました。 MainクラスはアプリケーションのエントリーポイントとしてのMainメソッドを提供し、プログラムの流れを記述します。 WaitExecuterクラスはウェイトを実行するためのクラスで、ウェイトが完了するとWaitOverMethodで指定されているメソッドを呼び出します。 WaitOverNotifierクラスは、ウェイトが終了したことを通知するためのクラスです。
~

          

          
~
// イベントが発生した時刻を保持するフィールドを持つイベント引数
#code(vb,指定した時間だけウェイトする・クラス化版){{
+
class WaitEventArgs : EventArgs
+
{
+
  public readonly DateTime DateTime;
+

          
+
  public WaitEventArgs(DateTime dateTime)
+
  {
+
    this.DateTime = dateTime;
+
  }
+
}
+

          
+
class WaitAndNotify
+
{
+
  // WaitEventArgsをイベント引数として使用するデリゲートを使ってイベントを定義
+
  public event EventHandler<WaitEventArgs> WaitStart;
+
  public event EventHandler<WaitEventArgs> WaitDone;
+

          
+
  public void Wait(TimeSpan timeout)
+
  {
+
    if (WaitStart != null) WaitStart(this, new WaitEventArgs(DateTime.Now));
+

          
+
    Thread.Sleep(timeout);
+

          
+
    if (WaitDone != null) WaitDone(this, new WaitEventArgs(DateTime.Now));
+
  }
+
}
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    WaitAndNotify w = new WaitAndNotify();
+

          
+
    w.WaitStart += new EventHandler<WaitEventArgs>(WaitStartHandler);
+
    w.WaitDone  += new EventHandler<WaitEventArgs>(WaitDoneHandler);
+

          
+
    w.Wait(TimeSpan.FromSeconds(3.0));
+
  }
+

          
+
  static void WaitStartHandler(object sender, WaitEventArgs e)
+
  {
+
    Console.WriteLine("{0:T}: start", e.DateTime);
+
  }
+

          
+
  static void WaitDoneHandler(object sender, WaitEventArgs e)
+
  {
+
    Console.WriteLine("{0:T}: done", e.DateTime);
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
 
Imports System
Imports System
 
Imports System.Threading
Imports System.Threading
 

        

        
~
' イベントが発生した時刻を保持するフィールドを持つイベント引数
' デリゲート型の宣言
~
Class WaitEventArgs
Public Delegate Sub WaitOverDelegate()
~
  Inherits EventArgs

          
~

          
Public Class Main
~
  Public Readonly DateTime As DateTime

          
~

          
    ' アプリケーションのエントリーポイント
~
  Public Sub New(ByVal dateTime As DateTime)
    Public Shared Function Main(ByVal args() As String) As Integer
~
    MyClass.DateTime = dateTime

          
~
  End Sub
        Dim e As New WaitExecuter()
-
        Dim n As New WaitOverNotifier()
-

          
-
        ' ウェイトが終了したことを通知するインスタンスメソッドを指定
-
        e.WaitOverMethod = AddressOf n.WaitOver
-

          
-
        Console.WriteLine("Start: {0}", DateTime.Now)
-

          
-
        e.Wait(3000)
-

          
-
        Console.WriteLine("Finished: {0}", DateTime.Now)
-

          
-
        Return 0
-

          
-
    End Function
-

          
 
End Class
End Class
 

        

        
~
Class WaitAndNotify
' ウェイトを行うためのクラス
~
  ' WaitEventArgsをイベント引数として使用するデリゲートを使ってイベントを定義
Public Class WaitExecuter
~
  Public Event WaitStart As EventHandler(Of WaitEventArgs)

          
~
  Public Event WaitDone As EventHandler(Of WaitEventArgs)
    Public WaitOverMethod As WaitOverDelegate
 

        

        
~
  Public Sub Wait(ByVal timeout As TimeSpan)
    ' 指定されたミリ秒だけウェイトする
~
    RaiseEvent WaitStart(Me, New WaitEventArgs(DateTime.Now))
    Public Sub Wait(ByVal millisec As Integer)
 

        

        
~
    Thread.Sleep(timeout)
        Thread.Sleep(millisec)
-

          
-
        ' ウェイト終了を通知
-
        WaitOverMethod()
-

          
-
    End Sub
 

        

        
+
    RaiseEvent WaitDone(Me, New WaitEventArgs(DateTime.Now))
+
  End Sub
 
End Class
End Class
 

        

        
~
Class Sample
' ウェイト終了を感知するためのクラス
~
  Shared Sub Main()
Public Class WaitOverNotifier
~
    Dim w As New WaitAndNotify()

          
~

          
    ' ウェイトが終了したことを知らせる
~
    AddHandler w.WaitStart, AddressOf WaitStartHandler
    Public Sub WaitOver()
~
    AddHandler w.WaitDone, AddressOf WaitDoneHandler

          
~

          
        Console.WriteLine("Wait over.")
~
    w.Wait(TimeSpan.FromSeconds(3.0))

          
~
  End Sub
    End Sub
~

          

          
+
  Shared Sub WaitStartHandler(ByVal sender As Object, ByVal e As WaitEventArgs)
+
    Console.WriteLine("{0:T}: start", e.DateTime)
+
  End SUb
+

          
+
  Shared Sub WaitDoneHandler(ByVal sender As Object, ByVal e As WaitEventArgs)
+
    Console.WriteLine("{0:T}: done", e.DateTime)
+
  End SUb
 
End Class
End Class
 
}}
}}
+
#tabpage-end
 

        

        
~
#prompt{{
#prompt(実行結果){{
~
3:26:55: start
Start: 2003/02/21 2:40:06
~
3:26:58: done
Wait over.
-
Finished: 2003/02/21 2:40:09
-
Press any key to continue
 
}}
}}
 

        

        
~
この例では、EventArgsクラスを継承したWaitEventArgsクラスを作成し、DateTime型の読み取り専用フィールドを追加しています。 読み取り専用にしているのは、イベントハンドラの側で値を変更できないようにするためです。 また、WaitEventArgsクラスを使ったイベントを定義するために、&msdn(netfx,id,db0etb8x){EventHandler<TEventArgs>デリゲート};を使っています。 このデリゲートの型パラメータTEventArgsには、EventArgsから派生した任意の型を指定することが出来ます。 この例ではEventHandler<WaitEventArgs>として使用しています。
見ての通り実行結果は予想通りで、インスタンスメソッドでもデリゲートはちゃんと機能します。 参考までに、デリゲートは異なるインスタンスのメソッドを区別します。 つまり、インスタンスAとインスタンスBのメソッドMは別のものとして扱われます。
-

          
-
次はイベントを定義することにします。
 

        

        
~
このようにして、独自に作成したWaitEventArgsを使ってイベントを発生させることが出来るようになりました。 これに合わせて、イベントハンドラの引数もWaitEventArgsにすることで発生したイベントを受信できるようになります。
*イベント
-
さて、ここでやっとなじみ深いイベントが出てくるわけです。 イベントはクラス内の状態の変化などを外部に伝える役割を持っています。 ところで、デリゲートはメソッドを特定する役割を持つとしました。 イベントは外部に情報を伝えるためにありますが、伝えるための各々のメソッド、つまりイベントハンドラはどんなメソッドでもいいわけではなく、引数や戻り値がすべて一致している必要があります。 ここで、イベントハンドラの型を定義するためにデリゲート型を用います。
 

        

        
~
EventHandler<TEventArgs>の代わりに、独自にデリゲートを宣言することも出来ます。 イベントハンドラとなるデリゲートの場合でもデリゲートには任意の名前を指定することは出来ますが、ガイドラインではイベントハンドラとなるデリゲートであることが分かるよう、デリゲートの名前はEventHandlerで終わるようにすることが推奨されています。 以下の例では、WaitEventArgsをイベント引数とするデリゲートWaitEventHandlerを使ってイベントを定義しています。
説明だけではよくわからないと思うので、実際にイベントとイベントハンドラを用いたサンプルを次に示します。 例によって、前のサンプルを少し改変したものです。
 

        

        
~
#tabpage(C#)
#code(vb,指定した時間だけウェイトする・イベント版){{
+
#code(cs){{
+
using System;
+
using System.Threading;
+

          
+
// イベントが発生した時刻を保持するフィールドを持つイベント引数
+
class WaitEventArgs : EventArgs
+
{
+
  public readonly DateTime DateTime;
+

          
+
  public WaitEventArgs(DateTime dateTime)
+
  {
+
    this.DateTime = dateTime;
+
  }
+
}
+

          
+
// WaitEventArgsをイベント引数として使用するデリゲート
+
delegate void WaitEventHandler(object sender, WaitEventArgs e);
+

          
+
class WaitAndNotify
+
{
+
  public event WaitEventHandler WaitStart;
+
  public event WaitEventHandler WaitDone;
+

          
+
  public void Wait(TimeSpan timeout)
+
  {
+
    if (WaitStart != null) WaitStart(this, new WaitEventArgs(DateTime.Now));
+

          
+
    Thread.Sleep(timeout);
+

          
+
    if (WaitDone != null) WaitDone(this, new WaitEventArgs(DateTime.Now));
+
  }
+
}
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    WaitAndNotify w = new WaitAndNotify();
+

          
+
    w.WaitStart += new WaitEventHandler(WaitStartHandler);
+
    w.WaitDone  += new WaitEventHandler(WaitDoneHandler);
+

          
+
    w.Wait(TimeSpan.FromSeconds(3.0));
+
  }
+

          
+
  static void WaitStartHandler(object sender, WaitEventArgs e)
+
  {
+
    Console.WriteLine("{0:T}: start", e.DateTime);
+
  }
+

          
+
  static void WaitDoneHandler(object sender, WaitEventArgs e)
+
  {
+
    Console.WriteLine("{0:T}: done", e.DateTime);
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
 
Imports System
Imports System
 
Imports System.Threading
Imports System.Threading
 

        

        
~
' イベントが発生した時刻を保持するフィールドを持つイベント引数
' デリゲート型の宣言
~
Class WaitEventArgs
Public Delegate Sub WaitOverDelegate()
~
  Inherits EventArgs

          
~

          
Public Class Main
~
  Public Readonly DateTime As DateTime

          
~

          
    ' アプリケーションのエントリーポイント
~
  Public Sub New(ByVal dateTime As DateTime)
    Public Shared Function Main(ByVal args() As String) As Integer
~
    MyClass.DateTime = dateTime

          
~
  End Sub
        Dim e As New WaitExecuter()
-
        Dim n As New WaitOverNotifier()
-

          
-
        ' ウェイトが終了したことを通知するイベントハンドラを指定
-
        AddHandler e.WaitOver, AddressOf n.WaitOver
-

          
-
        Console.WriteLine("Start: {0}", DateTime.Now)
-

          
-
        e.Wait(3000)
-

          
-
        Console.WriteLine("Finished: {0}", DateTime.Now)
-

          
-
        Return 0
-

          
-
    End Function
-

          
 
End Class
End Class
 

        

        
~
' WaitEventArgsをイベント引数として使用するデリゲート
' ウェイトを行うためのクラス
~
Delegate Sub WaitEventHandler(ByVal sender As Object, ByVal e As WaitEventArgs)
Public Class WaitExecuter
 

        

        
~
Class WaitAndNotify
    ' ウェイトが終了したことを通知するイベント
~
  Public Event WaitStart As WaitEventHandler
    Public Event WaitOver As WaitOverDelegate
+
  Public Event WaitDone As WaitEventHandler
 

        

        
~
  Public Sub Wait(ByVal timeout As TimeSpan)
    ' 指定されたミリ秒だけウェイトする
~
    RaiseEvent WaitStart(Me, New WaitEventArgs(DateTime.Now))
    Public Sub Wait(ByVal millisec As Integer)
 

        

        
~
    Thread.Sleep(timeout)
        Thread.Sleep(millisec)
-

          
-
        ' ウェイト終了イベントを発生
-
        RaiseEvent WaitOver()
-

          
-
    End Sub
 

        

        
+
    RaiseEvent WaitDone(Me, New WaitEventArgs(DateTime.Now))
+
  End Sub
 
End Class
End Class
 

        

        
~
Class Sample
' ウェイト終了を感知するためのクラス
~
  Shared Sub Main()
Public Class WaitOverNotifier
~
    Dim w As New WaitAndNotify()

          
~

          
    ' ウェイトが終了したことを知らせる
~
    AddHandler w.WaitStart, AddressOf WaitStartHandler
    Public Sub WaitOver()
~
    AddHandler w.WaitDone, AddressOf WaitDoneHandler

          
~

          
        Console.WriteLine("Wait over.")
~
    w.Wait(TimeSpan.FromSeconds(3.0))

          
~
  End Sub
    End Sub
~

          

          
+
  Shared Sub WaitStartHandler(ByVal sender As Object, ByVal e As WaitEventArgs)
+
    Console.WriteLine("{0:T}: start", e.DateTime)
+
  End SUb
+

          
+
  Shared Sub WaitDoneHandler(ByVal sender As Object, ByVal e As WaitEventArgs)
+
    Console.WriteLine("{0:T}: done", e.DateTime)
+
  End SUb
 
End Class
End Class
 
}}
}}
+
#tabpage-end
 

        

        
~
*&aname(multicasting){マルチキャスト};
#prompt(実行結果){{
~
イベントハンドラに指定するメソッドは、一つだけではなく複数指定することが出来ます。 これはデリゲートの持つ[[マルチキャスト>programming/netfx/delegate/2_operations#multicasting]]の機能により実現されるものです。 既に解説した一つのイベントハンドラを指定する場合と同様、C#では+=演算子、VBではAddHandlerステートメントを使って一つのイベントに複数のイベントハンドラを指定する事が出来ます。
Start: 2003/02/21 3:05:40
-
Wait over.
-
Finished: 2003/02/21 3:05:43
-
Press any key to continue
-
}}
-

          
-
まず、このコードで大きく変わったところから説明していきます。 まず、32行目ですが、以前はデリゲート型のパブリック変数が宣言されていましたが、ここではEventキーワードによりイベントとして宣言されています。 また、「Event (イベント名) As (デリゲート型)」とすることで、このイベントのハンドラはデリゲート型で指定されている形式と同じ形式を持たなければならなくなります。
-

          
-
イベント宣言へと変わったことで、16行目も変わりました。 AddHandlerステートメントは、イベントに適切なイベントハンドラを追加するためのものです。 参考までに、イベントのリストから削除するためにRemoveEventステートメントも用意されています。  AddHandlerステートメントは「AddHandler (イベント), AddressOf (イベントハンドラ)」のように記述します。 これにより16行目では、e.WaitOverイベントにn.WaitOver()をイベントハンドラとして追加することができます。
-

          
-
最後に、デリゲートの時はメソッド同様に呼び出すことができましたが、イベントの場合は異なります。 40行目にあるように、 RaiseEventステートメントでイベントを発生させます。 このとき、WaitOverに指定されている全てのイベントハンドラが呼び出されます。
 

        

        
~
#tabpage(C#)
この例ではイベントハンドラを一つしか設定しませんでしたが、一つのイベントに対して複数のイベントハンドラを設定することも可能です。 また、イベントも同様に複数定義できます。 この次では、イベントに引数を持たせることでクラス内の情報を詳細に伝えることを考えてみます。
~
#code(cs){{

          
~
using System;
*イベントの引数
~
using System.Threading;
ただ単にイベントハンドラを呼び出しただけでは、そのイベントが発生したときのクラス内の状態を知ることができません。 そのため、イベントに引数を持たせることを考えてみます。
~

          

          
~
class WaitAndNotify
VB.NETのコントロールなどのほとんどのイベントハンドラでは、イベントを発生させたオブジェクトを知るための sender と、そのイベントが発生した時点での状況などを記した e が、イベントハンドラの引数として取得できます。 具体的には sender は Object型、e は EventArgsクラスを継承した型です。
~
{

          
~
  public event EventHandler WaitStart;
このサンプルでも、その慣例に従って、EventArgsクラスを継承したクラスWaitOverEventArgsをイベントの引数として渡してみることにします。 このクラスにはウェイトが完了した時刻を格納させるようにします。 また、イベントに引数を持たせるため、デリゲートも多少変更を加え、名前もWaitOverEventHandlerに変えます。
~
  public event EventHandler WaitDone;

          
~

          
#code(vb,指定した時間だけウェイトする・引数付きイベント版)){{
+
  public void Wait(TimeSpan timeout)
+
  {
+
    if (WaitStart != null) WaitStart(this, EventArgs.Empty);
+

          
+
    Thread.Sleep(timeout);
+

          
+
    if (WaitDone != null) WaitDone(this, EventArgs.Empty);
+
  }
+
}
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    WaitAndNotify w = new WaitAndNotify();
+

          
+
    // WaitStart, WaitDoneイベントに複数のハンドラを指定
+
    w.WaitStart += WaitCommonHandler;
+
    w.WaitStart += WaitStartHandler1;
+
    w.WaitStart += WaitStartHandler2;
+

          
+
    w.WaitDone  += WaitDoneHandler1;
+
    w.WaitDone  += WaitCommonHandler;
+
    w.WaitDone  += WaitDoneHandler2;
+

          
+
    w.Wait(TimeSpan.FromSeconds(3.0));
+
  }
+

          
+
  static void WaitCommonHandler(object sender, EventArgs e)
+
  {
+
    Console.WriteLine("WaitCommonHandler");
+
  }
+

          
+
  static void WaitStartHandler1(object sender, EventArgs e)
+
  {
+
    Console.WriteLine("WaitStartHandler1");
+
  }
+

          
+
  static void WaitStartHandler2(object sender, EventArgs e)
+
  {
+
    Console.WriteLine("WaitStartHandler2");
+
  }
+

          
+
  static void WaitDoneHandler1(object sender, EventArgs e)
+
  {
+
    Console.WriteLine("WaitStartHandler1");
+
  }
+

          
+
  static void WaitDoneHandler2(object sender, EventArgs e)
+
  {
+
    Console.WriteLine("WaitStartHandler2");
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
 
Imports System
Imports System
 
Imports System.Threading
Imports System.Threading
 

        

        
~
Class WaitAndNotify
' デリゲート型の宣言
~
  Public Event WaitStart As EventHandler
Public Delegate Sub WaitOverEventHandler(ByVal sender As Object, ByVal e As WaitOverEventArgs)
~
  Public Event WaitDone As EventHandler

          
-
Public Class Main
-

          
-
    ' アプリケーションのエントリーポイント
-
    Public Shared Function Main(ByVal args() As String) As Integer
 

        

        
~
  Public Sub Wait(ByVal timeout As TimeSpan)
        Dim e As New WaitExecuter()
~
    RaiseEvent WaitStart(Me, EventArgs.Empty)
        Dim n As New WaitOverNotifier()
 

        

        
~
    Thread.Sleep(timeout)
        ' ウェイトが終了したことを通知するイベントハンドラを指定
-
        AddHandler e.WaitOver, AddressOf n.WaitOver
-

          
-
        Console.WriteLine("Start: {0}", DateTime.Now)
-

          
-
        e.Wait(3000)
-

          
-
        Return 0
-

          
-
    End Function
 

        

        
+
    RaiseEvent WaitDone(Me, EventArgs.Empty)
+
  End Sub
 
End Class
End Class
 

        

        
~
Class Sample

          
~
  Shared Sub Main()
' ウェイトが終了したときのイベント引数のためのクラス
~
    Dim w As New WaitAndNotify()
Public Class WaitOverEventArgs
~

          

          
~
    ' WaitStart, WaitDoneイベントに複数のハンドラを指定
    ' EventArgsを継承
~
    AddHandler w.WaitStart, AddressOf WaitCommonHandler
    Inherits EventArgs
~
    AddHandler w.WaitStart, AddressOf WaitStartHandler1

          
~
    AddHandler w.WaitStart, AddressOf WaitStartHandler2
    ' ウェイトが終了したときの時間
~

          
    Protected m_dtm As DateTime
~
    AddHandler w.WaitDone, AddressOf WaitDoneHandler1

          
~
    AddHandler w.WaitDone, AddressOf WaitCommonHandler
    ' 読み取り専用プロパティ
~
    AddHandler w.WaitDone, AddressOf WaitDoneHandler2
    Public ReadOnly Property WaitOverTime() As DateTime
~

          
        Get
~
    w.Wait(TimeSpan.FromSeconds(3.0))
            Return m_dtm
~
  End Sub
        End Get
~

          
    End Property
~
  Shared Sub WaitCommonHandler(ByVal sender As Object, ByVal e As EventArgs)

          
~
    Console.WriteLine("WaitCommonHandler")
    ' コンストラクタ
~
  End SUb
    Public Sub New(ByVal dtm As DateTime)
~

          
        MyBase.New()
~
  Shared Sub WaitStartHandler1(ByVal sender As Object, ByVal e As EventArgs)
        m_dtm = dtm
~
    Console.WriteLine("WaitStartHandler1")
    End Sub
~
  End SUb

          
+

          
+
  Shared Sub WaitStartHandler2(ByVal sender As Object, ByVal e As EventArgs)
+
    Console.WriteLine("WaitStartHandler2")
+
  End SUb
+

          
+
  Shared Sub WaitDoneHandler1(ByVal sender As Object, ByVal e As EventArgs)
+
    Console.WriteLine("WaitDoneHandler1")
+
  End SUb
+

          
+
  Shared Sub WaitDoneHandler2(ByVal sender As Object, ByVal e As EventArgs)
+
    Console.WriteLine("WaitDoneHandler2")
+
  End SUb
 
End Class
End Class
+
}}
+
#tabpage-end
 

        

        
+
#prompt{{
+
WaitCommonHandler
+
WaitStartHandler1
+
WaitStartHandler2
+
WaitDoneHandler1
+
WaitCommonHandler
+
WaitDoneHandler2
+
}}
 

        

        
~
結果から分かるとおり、指定したすべてのイベントハンドラが呼び出されています。 なお、複数のイベントハンドラを指定した場合は、指定した順(イベントに追加した順)でイベントハンドラが呼び出されるようになります。
' ウェイトを行うためのクラス
-
Public Class WaitExecuter
 

        

        
~
この様にして追加した複数のイベントハンドラから特定のハンドラを削除したい場合は、C#では-=演算子、VBでは&msdn(netfx,id,3xz97kac){RemoveHandlerステートメント};を使います。 当然、削除したイベントハンドラはイベントが発生しても呼び出されなくなります。
    ' ウェイトが終了したことを通知するイベント
-
    Public Event WaitOver As WaitOverEventHandler
 

        

        
~
#tabpage(C#)
    ' 指定されたミリ秒だけウェイトする
~
#code(cs){{
    Public Sub Wait(ByVal millisec As Integer)
+
using System;
+
using System.Threading;
+

          
+
class WaitAndNotify
+
{
+
  public event EventHandler WaitStart;
+
  public event EventHandler WaitDone;
+

          
+
  public void Wait(TimeSpan timeout)
+
  {
+
    if (WaitStart != null) WaitStart(this, EventArgs.Empty);
+

          
+
    Thread.Sleep(timeout);
+

          
+
    if (WaitDone != null) WaitDone(this, EventArgs.Empty);
+
  }
+
}
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    WaitAndNotify w = new WaitAndNotify();
+

          
+
    // WaitStart, WaitDoneイベントに複数のハンドラを指定
+
    w.WaitStart += WaitCommonHandler;
+
    w.WaitStart += WaitStartHandler1;
+
    w.WaitStart += WaitStartHandler2;
+

          
+
    w.WaitDone  += WaitDoneHandler1;
+
    w.WaitDone  += WaitCommonHandler;
+
    w.WaitDone  += WaitDoneHandler2;
+

          
+
    // 指定したイベントハンドラを削除
+
    w.WaitStart -= WaitStartHandler1;
+

          
+
    w.WaitDone  -= WaitDoneHandler2;
+

          
+
    w.Wait(TimeSpan.FromSeconds(3.0));
+
  }
+

          
+
  static void WaitCommonHandler(object sender, EventArgs e)
+
  {
+
    Console.WriteLine("WaitCommonHandler");
+
  }
+

          
+
  static void WaitStartHandler1(object sender, EventArgs e)
+
  {
+
    Console.WriteLine("WaitStartHandler1");
+
  }
+

          
+
  static void WaitStartHandler2(object sender, EventArgs e)
+
  {
+
    Console.WriteLine("WaitStartHandler2");
+
  }
+

          
+
  static void WaitDoneHandler1(object sender, EventArgs e)
+
  {
+
    Console.WriteLine("WaitDoneHandler1");
+
  }
+

          
+
  static void WaitDoneHandler2(object sender, EventArgs e)
+
  {
+
    Console.WriteLine("WaitDoneHandler2");
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
+
Imports System.Threading
 

        

        
~
Class WaitAndNotify
        Thread.Sleep(millisec)
+
  Public Event WaitStart As EventHandler
+
  Public Event WaitDone As EventHandler
 

        

        
~
  Public Sub Wait(ByVal timeout As TimeSpan)
        ' ウェイト終了イベントを発生
~
    RaiseEvent WaitStart(Me, EventArgs.Empty)
        RaiseEvent WaitOver(Me, New WaitOverEventArgs(DateTime.Now))
 

        

        
~
    Thread.Sleep(timeout)
    End Sub
 

        

        
+
    RaiseEvent WaitDone(Me, EventArgs.Empty)
+
  End Sub
 
End Class
End Class
 

        

        
~
Class Sample

          
~
  Shared Sub Main()
' ウェイト終了を感知するためのクラス
~
    Dim w As New WaitAndNotify()
Public Class WaitOverNotifier
~

          

          
~
    ' WaitStart, WaitDoneイベントに複数のハンドラを指定
    ' ウェイトが終了したことを知らせる
~
    AddHandler w.WaitStart, AddressOf WaitCommonHandler
    Public Sub WaitOver(ByVal sender As Object, ByVal e As WaitOverEventArgs)
~
    AddHandler w.WaitStart, AddressOf WaitStartHandler1

          
~
    AddHandler w.WaitStart, AddressOf WaitStartHandler2
        ' ウェイト終了時間を出力する
~

          
        Console.WriteLine("Wait over, {0}.", e.WaitOverTime)
~
    AddHandler w.WaitDone, AddressOf WaitDoneHandler1

          
~
    AddHandler w.WaitDone, AddressOf WaitCommonHandler
    End Sub
~
    AddHandler w.WaitDone, AddressOf WaitDoneHandler2

          
+

          
+
    ' 指定したイベントハンドラを削除
+
    RemoveHandler w.WaitStart, AddressOf WaitStartHandler1
+

          
+
    RemoveHandler w.WaitDone, AddressOf WaitDoneHandler2
+

          
+
    w.Wait(TimeSpan.FromSeconds(3.0))
+
  End Sub
+

          
+
  Shared Sub WaitCommonHandler(ByVal sender As Object, ByVal e As EventArgs)
+
    Console.WriteLine("WaitCommonHandler")
+
  End SUb
+

          
+
  Shared Sub WaitStartHandler1(ByVal sender As Object, ByVal e As EventArgs)
+
    Console.WriteLine("WaitStartHandler1")
+
  End SUb
+

          
+
  Shared Sub WaitStartHandler2(ByVal sender As Object, ByVal e As EventArgs)
+
    Console.WriteLine("WaitStartHandler2")
+
  End SUb
+

          
+
  Shared Sub WaitDoneHandler1(ByVal sender As Object, ByVal e As EventArgs)
+
    Console.WriteLine("WaitDoneHandler1")
+
  End SUb
+

          
+
  Shared Sub WaitDoneHandler2(ByVal sender As Object, ByVal e As EventArgs)
+
    Console.WriteLine("WaitDoneHandler2")
+
  End SUb
 
End Class
End Class
 
}}
}}
+
#tabpage-end
 

        

        
~
#prompt{{
#prompt(実行結果){{
~
WaitCommonHandler
Start: 2003/02/21 3:51:39
~
WaitStartHandler2
Wait over, 2003/02/21 3:51:42.
~
WaitDoneHandler1
Press any key to continue
+
WaitCommonHandler
 
}}
}}
 

        

        
~
なお、一つのイベントに同じメソッドのイベントハンドラが複数追加されている場合、削除の際に同じメソッドのイベントハンドラが一度に削除されます。
まず、イベント引数のためのクラスWaitOverEventArgsが新しく定義されました。 このクラスはイベントが発生した時間を保持する以外の役割はありません。 64行目のRaiseEventステートメントでは、WaitOverイベントのデリゲート型が引数をとるように変わったので、それに伴い二つの引数を渡しています。 ここで、新しく作成されたWaitOverEventArgsを渡すことで、イベントが発生した時刻を知らせるわけです。 また、senderにMeを渡すことで、イベントを発生させた張本人である自分自身をイベントハンドラに通知します。
 

        

        
~
#navi(..)
さらに、イベントハンドラの方も変更を加えます。 75行目のWaitOverイベントハンドラも、デリゲート型のパラメータ形式が変わったのでそれに伴い形式を変更します。 ここで、このイベントハンドラが呼び出されたとき、引数として渡されるeのWaitOverTimeプロパティにはこのイベントが発生した時点での時刻が格納されているので、これを表示することでウェイト終了時刻を表示してやります。
 

        

        
-
このように、ほとんど同じ結果が得られるにも関わらず、イベントを使う前の一番最初のコードと、イベントを定義した最後のコードでは、全く違うプログラムかのように見えるくらい両者は異なったものになっています。 しかし、デリゲートやイベントはクラスの状態変化を通知したりするのに非常に強力なものになるので、ぜひマスターするといいでしょう。
 

        

        

programming/netfx/delegate/2_operations/index.wiki.txt

current previous
1,898 1,391
~
${smdncms:title,デリゲートの機能}
${smdncms:title,イベントとデリゲート}
~
${smdncms:header_title,デリゲートの機能 (System.Delegate)}
${smdncms:keywords,イベント,デリゲート,Delegate,Event,EventHandler,EventArgs,AddHandler}
+
${smdncms:keywords,マルチキャスト,MulticastDelegate,BeginInvoke,EndInvoke,Combine,Remove,RemoveAll}
 
${smdncms:meta,toc-amazonlivelink-keyword,books-jp,.net framework}
${smdncms:meta,toc-amazonlivelink-keyword,books-jp,.net framework}
-
Visual Basicではコマンドボタンがクリックされたときとか、テキストボックスに文字が入力されたときなどにイベントとしてメッセージが通知されてきました。 Visual Basic .NETでも同様のイベントというものが存在します。 また、Visual Basicでのクラス同様、独自のイベントを定義することができます。
 

        

        
~
#navi(..)
しかしVisual Basic .NETになってからイベントにまつわる部分が大きく変わりました。 個人的には.NETのイベントの方がわかりやすくなったかと思います。 ここではこのイベントと、それと切っても切り離せない関係にあるデリゲートについて考えてみたいと思います。
 

        

        
~
delegateキーワードにより宣言したデリゲートやライブラリで提供されるデリゲートは暗黙的に&msdn(netfx,type,System.MulticastDelegate){MulticastDelegateクラス};を継承していて、デリゲートの基本的な機能はその基底クラスである&msdn(netfx,type,System.Delegate){Delegateクラス};により提供されます。 ここではDelegateクラスの持ついくつかのメソッド・メンバとデリゲートの機能について見ていきます。
#googleadunit
 

        

        
~
----
*導入
-
まず、イベント・デリゲートをいきなり使う前に、これからイベントとデリゲートの説明に使うサンプルコードを提示します。
 

        

        
~
*&aname(multicasting){マルチキャスト};とデリゲートの連結・削除 (Combine, Remove)
#code(vb,指定した時間だけウェイトする){{
~
[[イベントのマルチキャスト>programming/netfx/delegate/1_events#multicasting]]で解説したとおり、デリゲートには一つのデリゲートで複数のメソッドを呼び出すことが出来るようになっています。 これは、一つのデリゲートを別のデリゲートと連結することによりマルチキャストするデリゲートを作成することで実現されます。
Imports System
-
Imports System.Threading
-

          
-
Module Main
 

        

        
~
デリゲートの連結には&msdn(netfx,method,System.Delegate.Combine){Combineメソッド};、デリゲートからのメソッドの削除には&msdn(netfx,method,System.Delegate.Remove){Removeメソッド};もしくは&msdn(netfx,method,System.Delegate.RemoveAll){RemoveAllメソッド};を使います。 なお、Delegateクラスは不変クラスです。 そのため、これらのメソッドは静的メソッドとして存在し、個々のインスタンスへの変更は行われません。 メソッドを呼び出すと、連結・削除を行った結果を含む新たなインスタンスを返すようになっています。
    ' アプリケーションのエントリーポイント
-
    Public Function Main(ByVal args() As String) As Integer
 

        

        
~
以下はCombineメソッドとRemoveメソッドを使ってデリゲートの連結と削除を行う例です。
        Console.WriteLine("Start: {0}", DateTime.Now)
 

        

        
~
#tabpage(C#)
        Wait(3000)
+
#code(cs){{
+
using System;
 

        

        
~
class Sample
        Console.WriteLine("Finished: {0}", DateTime.Now)
+
{
+
  static void Main()
+
  {
+
    Action a = Print1;
 

        

        
~
    // デリゲートの連結
        Return 0
+
    a = Delegate.Combine(a, new Action(Print3)) as Action;
 

        

        
~
    // 連結したデリゲートの呼び出し
    End Function
+
    a();
 

        

        
~
    // デリゲートの削除
    ' 指定されたミリ秒だけウェイトする
~
    a = Delegate.Remove(a, new Action(Print3)) as Action;
    Public Sub Wait(ByVal millisec As Integer)
 

        

        
~
    a = Delegate.Combine(a, new Action(Print2)) as Action;
        Thread.Sleep(millisec)
+
    a = Delegate.Combine(a, new Action(Print3)) as Action;
 

        

        
~
    a();
        WaitOver()
+
  }
 

        

        
~
  static void Print1()
    End Sub
+
  {
+
    Console.Write("Hello, ");
+
  }
 

        

        
~
  static void Print2()
    ' ウェイトが終了したことを知らせる
~
  {
    Public Sub WaitOver()
+
    Console.Write("world!");
+
  }
 

        

        
~
  static void Print3()
        Console.WriteLine("Wait over.")
~
  {

          
~
    Console.WriteLine();
    End Sub
~
  }

          
~
}
End Module
 
}}
}}
~
#tabpage(VB)

          
~
#code(vb){{
#prompt(実行結果){{
-
Start: 2003/02/21 0:00:39
-
Wait over.
-
Finished: 2003/02/21 0:00:42
-
Press any key to continue
-
}}
-

          
-
ソースコードと実行結果とを見比べてもらえばすぐにわかりますが、複雑なことは何もやっていません。 63行目のThread.Sleep()は指定されたミリ秒だけ現在のスレッドを停止するというメソッドです。 Main()メソッドではWait()メソッドを呼び出す前後に、現在の時刻を表示するようにしています。 また、このサンプルでは3000ミリ秒、つまり3秒の間ウェイトするようにしています。 実行結果を見ればわかるように、始まりと終わりの間がちゃんと3秒になっています。
-

          
-
つぎに、このソースコードをデリゲートを使ったものに書き換えることにします。
-

          
-
*デリゲート
-
まず最初にデリゲートについて考えてみることにします。 まず、この文章を読んで勉強しようと思われる方の多くは、「イベントは知っているけどデリゲートなんて聞いたことがない」というような方だと思います。 当初僕自身も、デリゲートが理解不能でした。 C++を勉強した方の中には「関数ポインタ」というものをご存じの方もいると思いますが、デリゲートの役割はこの関数ポインタに似ているものといえます(厳密には異なるものです)。
-

          
-
というのも、デリゲートはあるメソッド(共有メソッド及びインスタンスメソッド)を特定する役割を持っているためです。 つまりその役割は、直接特定のメソッドを呼び出すのではなく、デリゲートによってそのデリゲートに指定されているメソッドを間接的に呼び出すことができるわけです。 イベントを発生するときに呼び出すべきメソッドをするためにデリゲートが使われます。 言い換えると、イベントはデリゲートに指定されているメソッドを呼び出します。
-

          
-
ここではまずイベントを用いず、先ほどのサンプルをデリゲートだけを用いたサンプルに作り替えてみます。
-

          
-
#code(vb,指定した時間だけウェイトする・デリゲート版){{
 
Imports System
Imports System
-
Imports System.Threading
-

          
-
Module Main
-

          
-
    Public Delegate Sub WaitOverDelegate()
-

          
-
    Dim WaitOverMethod As WaitOverDelegate
-

          
-
    ' アプリケーションのエントリーポイント
-
    Public Function Main(ByVal args() As String) As Integer
-

          
-
        Console.WriteLine("Start: {0}", DateTime.Now)
-

          
-
        'WaitOverMethod = AddressOf WaitOver
-
        WaitOverMethod = New WaitOverDelegate(AddressOf WaitOver)
-

          
-
        Wait(3000)
 

        

        
~
Class Sample
        Console.WriteLine("Finished: {0}", DateTime.Now)
+
  Shared Sub Main()
+
    Dim a As Action = Addressof Print1
 

        

        
~
    ' デリゲートの連結
        Return 0
+
    a = DirectCast([Delegate].Combine(a, New Action(AddressOf Print3)), Action)
 

        

        
~
    ' 連結したデリゲートの呼び出し
    End Function
+
    a()
 

        

        
~
    ' デリゲートの削除
    ' 指定されたミリ秒だけウェイトする
~
    a = DirectCast([Delegate].Remove(a, New Action(AddressOf Print3)), Action)
    Public Sub Wait(ByVal millisec As Integer)
 

        

        
~
    a = DirectCast([Delegate].Combine(a, New Action(AddressOf Print2)), Action)
        Thread.Sleep(millisec)
+
    a = DirectCast([Delegate].Combine(a, New Action(AddressOf Print3)), Action)
 

        

        
~
    a()
        WaitOverMethod()
+
  End Sub
 

        

        
~
  Shared Sub Print1()
    End Sub
+
    Console.Write("Hello, ")
+
  End Sub
 

        

        
~
  Shared Sub Print2()
    ' ウェイトが終了したことを知らせる
~
    Console.Write("world!")
    Public Sub WaitOver()
+
  End Sub
 

        

        
~
  Shared Sub Print3()
        Console.WriteLine("Wait over.")
~
    Console.WriteLine()

          
~
  End Sub
    End Sub
~
End Class

          
-
End Module
 
}}
}}
+
#tabpage-end
 

        

        
~
#prompt{{
#prompt(実行結果){{
~
Hello, 
Start: 2003/02/21 0:00:39
~
Hello, world!
Wait over.
-
Finished: 2003/02/21 0:00:42
-
Press any key to continue
 
}}
}}
 

        

        
~
なお、C#ではイベントの場合と同様に+=演算子と-=演算子を使ってデリゲートの連結・削除を行うことが出来るようになっています。
早速デリゲートが出てきましたが、落ち着いてみてみましょう。 このサンプルではWaitOverメソッドをデリゲートにより呼び出すことを試みています。 まず、6行目では新しいデリゲート型の宣言を行っています。 デリゲートではどんなメソッドでも呼び出せるわけではなく、引数・戻り値が一致するメソッドだけを呼び出せるので、そのデリゲートがどのようなメソッドを呼び出せるかを定義します。 ここでは、戻り値無し、引数無しのメソッドを WaitOverDelegateとしています。
-

          
-
続いて8行目はたった今宣言したデリゲート型のインスタンスを格納するために、WaitOverDelegate型のオブジェクト変数 WaitOverMethodを宣言しています。 WaitOverDelegate型とおなじ形式のメソッドを呼び出すためにはこの変数に呼び出したいメソッドを指定します。 実際に指定しているのが15・16行目です。 コメント化されている部分は、このコードでも意味は変わらないということを示しているので、どちらか好きな方でコーディングすることができます。 このコードのようにデリゲート型の変数に呼び出させたいメソッドを指定するには、「AddressOf (対象のメソッド)」という風に指定してやります。 ここで、対象のメソッドは、引数・戻り値がデリゲート型で指定されているものと一致する共有メソッドまたはアクセス権のあるインスタンスメソッドです。
-

          
-
さて、デリゲート型の変数に呼び出させたいメソッドを指定することができたら、後は呼び出すだけです。 実際に呼び出しを行っているのが29行目です。 一見WaitOverMethod()というメソッドを呼び出しているように見えますが、実際にはデリゲート型変数 WaitOverMethod()に指定されているメソッドであるWaitOver()が呼び出されています。 ですから、直接WaitOver()メソッドを呼び出すようなコードは記述されていなくても、デリゲートが間接的に呼び出してくれるわけです。
 

        

        
~
#code(cs){{
このサンプルをさらに改変して、インスタンスのメソッドも呼び出せるかどうかを実際に見てみます。
+
using System;
 

        

        
~
class Sample
*デリゲートでインスタンスメソッドを呼び出す
~
{
前のサンプルをクラス化して改変を加えたものが次のソースコードです。 ざっとコードの概要を説明しますと、今回使用しているデリゲート型WaitOverDelegateは、各クラス共通に用いられるのでPublicとし、クラス外で宣言します。
+
  static void Main()
+
  {
+
    Action a = Print1;
+

          
+
    // デリゲートの連結
+
    a += Print3;
+

          
+
    // 連結したデリゲートの呼び出し
+
    a();
+

          
+
    // デリゲートの削除
+
    a -= Print3;
+

          
+
    a += Print2;
+
    a += Print3;
+

          
+
    a();
+
  }
+

          
+
  static void Print1()
+
  {
+
    Console.Write("Hello, ");
+
  }
+

          
+
  static void Print2()
+
  {
+
    Console.Write("world!");
+
  }
+

          
+
  static void Print3()
+
  {
+
    Console.WriteLine();
+
  }
+
}
+
}}
 

        

        
~
*&aname(invocationlist){呼び出されるメソッド・インスタンスの取得};(Method, Target, GetInvocationList)
さらに、機能別に三つのクラスに分割しました。 MainクラスはアプリケーションのエントリーポイントとしてのMainメソッドを提供し、プログラムの流れを記述します。 WaitExecuterクラスはウェイトを実行するためのクラスで、ウェイトが完了するとWaitOverMethodで指定されているメソッドを呼び出します。 WaitOverNotifierクラスは、ウェイトが終了したことを通知するためのクラスです。
+
デリゲートの&msdn(netfx,member,System.Delegate.Method){Methodプロパティ};を参照することでデリゲートに指定されているメソッドの&msdn(netfx,member,System.Reflection.MethodInfo){MethodInfo};を取得することが出来ます。 また、&msdn(netfx,member,System.Delegate.Target){Targetプロパティ};を参照することで、呼び出される対象のインスタンスを取得することが出来ます。
 

        

        
~
#tabpage(C#)
#code(vb,指定した時間だけウェイトする・クラス化版){{
+
#code(cs){{
+
using System;
+

          
+
class ConsolePrint
+
{
+
  private int id;
+
  private string message;
+

          
+
  public ConsolePrint(int id, string message)
+
  {
+
    this.id = id;
+
    this.message = message;
+
  }
+

          
+
  public void Print()
+
  {
+
    Console.WriteLine(message);
+
  }
+

          
+
  public override string ToString()
+
  {
+
    return string.Format("{0}(id = {1})", GetType().Name, id);
+
  }
+
}
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    ConsolePrint p1 = new ConsolePrint(1, "Hello,");
+
    ConsolePrint p2 = new ConsolePrint(2, "world!");
+

          
+
    Action a1 = p1.Print;
+
    Action a2 = p2.Print;
+

          
+
    Console.WriteLine("a1: {0}; {1}", a1.Target, a1.Method);
+

          
+
    a1();
+

          
+
    Console.WriteLine("a2: {0}; {1}", a2.Target, a2.Method);
+

          
+
    a2();
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
 
Imports System
Imports System
-
Imports System.Threading
 

        

        
~
Class ConsolePrint
' デリゲート型の宣言
~
  Private id As Integer
Public Delegate Sub WaitOverDelegate()
+
  Private message As String
+

          
+
  Public Sub New(ByVal id As Integer, ByVal message As String)
+
    MyClass.id = id
+
    MyClass.message = message
+
  End Sub
+

          
+
  Public Sub Print()
+
    Console.WriteLine(message)
+
  End Sub
+

          
+
  Public Overrides Function ToString() As String
+
    Return String.Format("{0}(id = {1})", Me.GetType().Name, id)
+
  End Function
+
End Class
 

        

        
~
Class Sample
Public Class Main
+
  Shared Sub Main()
+
    Dim p1 As New ConsolePrint(1, "Hello,")
+
    Dim p2 As New ConsolePrint(2, "world!")
 

        

        
~
    Dim a1 As Action = AddressOf p1.Print
    ' アプリケーションのエントリーポイント
~
    Dim a2 As Action = AddressOf p2.Print
    Public Shared Function Main(ByVal args() As String) As Integer
 

        

        
~
    Console.WriteLine("a1: {0}; {1}", a1.Target, a1.Method)
        Dim e As New WaitExecuter()
-
        Dim n As New WaitOverNotifier()
 

        

        
~
    a1()
        ' ウェイトが終了したことを通知するインスタンスメソッドを指定
-
        e.WaitOverMethod = AddressOf n.WaitOver
 

        

        
~
    Console.WriteLine("a2: {0}; {1}", a2.Target, a2.Method)
        Console.WriteLine("Start: {0}", DateTime.Now)
 

        

        
~
    a2()
        e.Wait(3000)
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
#prompt{{
        Console.WriteLine("Finished: {0}", DateTime.Now)
+
a1: ConsolePrint(id = 1); Void Print()
+
Hello,
+
a2: ConsolePrint(id = 2); Void Print()
+
world!
+
}}
 

        

        
~
なお、静的(共有)メソッドの場合はTargetプロパティがnull(Nothing)になります。 MethodInfo.IsStaticプロパティを参照することでメソッドが静的かどうか調べることが出来ます。
        Return 0
 

        

        
~
#tabpage(C#)
    End Function
+
#code(cs){{
+
using System;
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    Action a = Print;
+

          
+
    Console.WriteLine("a.Target: {0}", (a.Target == null ? "null" : a.Target.ToString()));
+
    Console.WriteLine("a.Method: {0} (IsStatic={1})", a.Method, a.Method.IsStatic);
+

          
+
    a();
+
  }
+

          
+
  static void Print()
+
  {
+
    Console.WriteLine("Hello, world!");
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
 

        

        
+
Class Sample
+
  Shared Sub Main()
+
    Dim a As Action = AddressOf Print
+

          
+
    Console.WriteLine("a.Target: {0}", If(a.Target Is Nothing, "Nothing", a.Target.ToString()))
+
    Console.WriteLine("a.Method: {0} (IsStatic={1})", a.Method, a.Method.IsStatic)
+

          
+
    a()
+
  End Sub
+

          
+
  Shared Sub Print()
+
    Console.WriteLine("Hello, world!")
+
  End Sub
 
End Class
End Class
+
}}
+
#tabpage-end
 

        

        
~
#prompt{{
' ウェイトを行うためのクラス
~
a.Target: null
Public Class WaitExecuter
+
a.Method: Void Print() (IsStatic=True)
+
Hello, world!
+
}}
 

        

        
~
また、連結されたデリゲートの場合、MethodプロパティとTargetプロパティはデリゲートの呼び出しリストにある一番最後のメソッドとインスタンスを返します。 呼び出しリストに含まれる個々のデリゲートを取得するには&msdn(netfx,method,System.Delegate.GetInvocationList){GetInvocationListメソッド};を使います。
    Public WaitOverMethod As WaitOverDelegate
 

        

        
~
#tabpage(C#)
    ' 指定されたミリ秒だけウェイトする
~
#code(cs){{
    Public Sub Wait(ByVal millisec As Integer)
+
using System;
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    Action a = Print1;
+

          
+
    a += Print2;
+
    a += Print3;
+

          
+
    Console.WriteLine("a.Method: {0}", a.Method);
+

          
+
    Console.WriteLine("[GetInvocationList]");
+

          
+
    foreach (Delegate d in a.GetInvocationList())
+
    {
+
      Console.WriteLine(d.Method);
+
    }
+

          
+
    a();
+
  }
+

          
+
  static void Print1()
+
  {
+
    Console.Write("Hello, ");
+
  }
+

          
+
  static void Print2()
+
  {
+
    Console.Write("world!");
+
  }
+

          
+
  static void Print3()
+
  {
+
    Console.WriteLine();
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
 

        

        
~
Class Sample
        Thread.Sleep(millisec)
+
  Shared Sub Main()
+
    Dim a As Action = Addressof Print1
 

        

        
~
    a = DirectCast([Delegate].Combine(a, New Action(AddressOf Print2)), Action)
        ' ウェイト終了を通知
~
    a = DirectCast([Delegate].Combine(a, New Action(AddressOf Print3)), Action)
        WaitOverMethod()
 

        

        
~
    Console.WriteLine("a.Method: {0}", a.Method)
    End Sub
 

        

        
~
    Console.WriteLine("[GetInvocationList]")
End Class
 

        

        
~
    For Each d As [Delegate] In a.GetInvocationList()
' ウェイト終了を感知するためのクラス
~
      Console.WriteLine(d.Method)
Public Class WaitOverNotifier
+
    Next
 

        

        
~
    a()
    ' ウェイトが終了したことを知らせる
~
  End Sub
    Public Sub WaitOver()
 

        

        
~
  Shared Sub Print1()
        Console.WriteLine("Wait over.")
+
    Console.Write("Hello, ")
+
  End Sub
 

        

        
~
  Shared Sub Print2()
    End Sub
+
    Console.Write("world!")
+
  End Sub
 

        

        
+
  Shared Sub Print3()
+
    Console.WriteLine()
+
  End Sub
 
End Class
End Class
 
}}
}}
+
#tabpage-end
 

        

        
~
#prompt{{
#prompt(実行結果){{
~
a.Method: Void Print3()
Start: 2003/02/21 2:40:06
~
[GetInvocationList]
Wait over.
~
Void Print1()
Finished: 2003/02/21 2:40:09
~
Void Print2()
Press any key to continue
+
Void Print3()
+
Hello, world!
 
}}
}}
 

        

        
~
*複製 (Clone)
見ての通り実行結果は予想通りで、インスタンスメソッドでもデリゲートはちゃんと機能します。 参考までに、デリゲートは異なるインスタンスのメソッドを区別します。 つまり、インスタンスAとインスタンスBのメソッドMは別のものとして扱われます。
+
デリゲートを複製するには&msdn(netfx,method,System.Delegate.Clone){Cloneメソッド};が使えます。 このメソッドは、簡易コピーを作成します。
+

          
+
#tabpage(C#)
+
#code(cs){{
+
using System;
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    Action a1 = Print;
+
    Action a2 = a1.Clone() as Action; // a1の複製を作成
+

          
+
    Console.WriteLine("a1.Method: {0}", a1.Method);
+
    Console.WriteLine("a2.Method: {0}", a2.Method);
+

          
+
    a1();
+
    a2();
+
  }
+

          
+
  static void Print()
+
  {
+
    Console.WriteLine("Hello, world!");
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
 

        

        
~
Class Sample
次はイベントを定義することにします。
+
  Shared Sub Main()
+
    Dim a1 As Action = Addressof Print
+
    Dim a2 As Action = DirectCast(a1.Clone(), Action) ' a1の複製を作成
+

          
+
    Console.WriteLine("a1.Method: {0}", a1.Method)
+
    Console.WriteLine("a2.Method: {0}", a2.Method)
+

          
+
    a1()
+
    a2()
+
  End Sub
+

          
+
  Shared Sub Print()
+
    Console.WriteLine("Hello, world!")
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
#prompt{{
*イベント
~
a1.Method: Void Print()
さて、ここでやっとなじみ深いイベントが出てくるわけです。 イベントはクラス内の状態の変化などを外部に伝える役割を持っています。 ところで、デリゲートはメソッドを特定する役割を持つとしました。 イベントは外部に情報を伝えるためにありますが、伝えるための各々のメソッド、つまりイベントハンドラはどんなメソッドでもいいわけではなく、引数や戻り値がすべて一致している必要があります。 ここで、イベントハンドラの型を定義するためにデリゲート型を用います。
+
a2.Method: Void Print()
+
Hello, world!
+
Hello, world!
+
}}
 

        

        
~
複数のメソッドが指定されているデリゲートも同様に複製されます。
説明だけではよくわからないと思うので、実際にイベントとイベントハンドラを用いたサンプルを次に示します。 例によって、前のサンプルを少し改変したものです。
 

        

        
~
#tabpage(C#)
#code(vb,指定した時間だけウェイトする・イベント版){{
+
#code(cs){{
+
using System;
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    Action a1 = Print1;
+

          
+
    a1 += Print2;
+

          
+
    Action a2 = a1.Clone() as Action; // a1の複製を作成
+

          
+
    Console.WriteLine("[a1.GetInvocationList]");
+

          
+
    foreach (Delegate d in a1.GetInvocationList())
+
    {
+
      Console.WriteLine(d.Method);
+
    }
+

          
+
    Console.WriteLine("[a2.GetInvocationList]");
+

          
+
    foreach (Delegate d in a2.GetInvocationList())
+
    {
+
      Console.WriteLine(d.Method);
+
    }
+

          
+
    a1();
+
    a2();
+
  }
+

          
+
  static void Print1()
+
  {
+
    Console.Write("Hello, ");
+
  }
+

          
+
  static void Print2()
+
  {
+
    Console.WriteLine("world!");
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
 
Imports System
Imports System
-
Imports System.Threading
-

          
-
' デリゲート型の宣言
-
Public Delegate Sub WaitOverDelegate()
 

        

        
~
Class Sample
Public Class Main
+
  Shared Sub Main()
+
    Dim a1 As Action = Addressof Print1
 

        

        
~
    a1 = DirectCast([Delegate].Combine(a1, New Action(AddressOf Print2)), Action)
    ' アプリケーションのエントリーポイント
-
    Public Shared Function Main(ByVal args() As String) As Integer
 

        

        
~
    Dim a2 As Action = DirectCast(a1.Clone(), Action) ' a1の複製を作成
        Dim e As New WaitExecuter()
-
        Dim n As New WaitOverNotifier()
 

        

        
~
    Console.WriteLine("[a1.GetInvocationList]")
        ' ウェイトが終了したことを通知するイベントハンドラを指定
-
        AddHandler e.WaitOver, AddressOf n.WaitOver
 

        

        
~
    For Each d As [Delegate] In a1.GetInvocationList()
        Console.WriteLine("Start: {0}", DateTime.Now)
+
      Console.WriteLine(d.Method)
+
    Next
 

        

        
~
    Console.WriteLine("[a2.GetInvocationList]")
        e.Wait(3000)
 

        

        
~
    For Each d As [Delegate] In a2.GetInvocationList()
        Console.WriteLine("Finished: {0}", DateTime.Now)
+
      Console.WriteLine(d.Method)
+
    Next
 

        

        
~
    a1()
        Return 0
+
    a2()
+
  End Sub
 

        

        
~
  Shared Sub Print1()
    End Function
+
    Console.Write("Hello, ")
+
  End Sub
 

        

        
+
  Shared Sub Print2()
+
    Console.WriteLine("world!")
+
  End Sub
 
End Class
End Class
+
}}
+
#tabpage-end
 

        

        
~
#prompt{{
' ウェイトを行うためのクラス
~
[a1.GetInvocationList]
Public Class WaitExecuter
~
Void Print1()

          
~
Void Print2()
    ' ウェイトが終了したことを通知するイベント
~
[a2.GetInvocationList]
    Public Event WaitOver As WaitOverDelegate
+
Void Print1()
+
Void Print2()
+
Hello, world!
+
Hello, world!
+
}}
 

        

        
~
*等価性の比較 (Equals, ==演算子, !=演算子)
    ' 指定されたミリ秒だけウェイトする
~
二つのデリゲートの等価性を比較するには、&msdn(netfx,method,System.Delegate.Equals){Equalsメソッド};、等価演算子==、不等価演算子!=(<>)が使えます。 デリゲート同士の比較では
    Public Sub Wait(ByVal millisec As Integer)
 

        

        
~
+二つのデリゲートの型が同じであり
        Thread.Sleep(millisec)
+
+かつ、二つのデリゲートの呼び出しリストにある個々のメソッドが、すべて同じインスタンスの同じメソッドである場合
 

        

        
~
等しいと判断されます。 なお、.NET Framework 1.xではデリゲートの型が異なっていても呼び出しリストの内容が等しい場合、二つのデリゲートは判断されます。 また、等価演算子・不等価演算子では両辺のデリゲートが同じ型でないと比較出来ませんが、Equalsメソッドでは異なるデリゲート型やデリゲート以外の型でも比較することは出来ます(戻り値はfalseとなります)。
        ' ウェイト終了イベントを発生
-
        RaiseEvent WaitOver()
 

        

        
~
#tabpage(C#)
    End Sub
+
#code(cs){{
+
using System;
+

          
+
class ConsolePrint
+
{
+
  public void Print()
+
  {
+
    Console.WriteLine("Hello, world!");
+
  }
+
}
+

          
+
delegate void DoPrint(); // Actionと同じシグネチャのデリゲート
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    Console.WriteLine(Environment.Version);
+
    Console.WriteLine();
+

          
+
    ConsolePrint p1 = new ConsolePrint();
+
    ConsolePrint p2 = new ConsolePrint();
+

          
+
    Action a1 = p1.Print;
+
    Action a2 = p2.Print;
+
    Action a3 = p1.Print;
+

          
+
    DoPrint d1 = p1.Print;
+

          
+
    Console.WriteLine("a1 == a2 : {0}", a1 == a2);
+
    Console.WriteLine("a1 == a3 : {0}", a1 == a3);
+

          
+
    Console.WriteLine("a1.Equals(a2): {0}", a1.Equals(a2));
+
    Console.WriteLine("a1.Equals(a3): {0}", a1.Equals(a3));
+
    Console.WriteLine("a1.Equals(d1) : {0}", a1.Equals(d1));
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
 

        

        
+
Class ConsolePrint
+
  Public Sub Print()
+
    Console.WriteLine("Hello, world!")
+
  End Sub
 
End Class
End Class
 

        

        
~
Delegate Sub DoPrint() ' Actionと同じシグネチャのデリゲート
' ウェイト終了を感知するためのクラス
-
Public Class WaitOverNotifier
-

          
-
    ' ウェイトが終了したことを知らせる
-
    Public Sub WaitOver()
 

        

        
~
Class Sample
        Console.WriteLine("Wait over.")
+
  Shared Sub Main()
+
    Console.WriteLine(Environment.Version)
+
    Console.WriteLine()
 

        

        
~
    Dim p1 As New ConsolePrint()
    End Sub
+
    Dim p2 As New ConsolePrint()
 

        

        
~
    Dim a1 As Action = AddressOf p1.Print
End Class
~
    Dim a2 As Action = AddressOf p2.Print
}}
+
    Dim a3 As Action = AddressOf p1.Print
 

        

        
~
    Dim d1 As DoPrint = AddressOf p1.Print
#prompt(実行結果){{
-
Start: 2003/02/21 3:05:40
-
Wait over.
-
Finished: 2003/02/21 3:05:43
-
Press any key to continue
-
}}
 

        

        
~
    Console.WriteLine("a1 = a2 : {0}", a1 = a2)
まず、このコードで大きく変わったところから説明していきます。 まず、32行目ですが、以前はデリゲート型のパブリック変数が宣言されていましたが、ここではEventキーワードによりイベントとして宣言されています。 また、「Event (イベント名) As (デリゲート型)」とすることで、このイベントのハンドラはデリゲート型で指定されている形式と同じ形式を持たなければならなくなります。
+
    Console.WriteLine("a1 = a3 : {0}", a1 = a3)
 

        

        
~
    Console.WriteLine("a1.Equals(a2): {0}", a1.Equals(a2))
イベント宣言へと変わったことで、16行目も変わりました。 AddHandlerステートメントは、イベントに適切なイベントハンドラを追加するためのものです。 参考までに、イベントのリストから削除するためにRemoveEventステートメントも用意されています。  AddHandlerステートメントは「AddHandler (イベント), AddressOf (イベントハンドラ)」のように記述します。 これにより16行目では、e.WaitOverイベントにn.WaitOver()をイベントハンドラとして追加することができます。
+
    Console.WriteLine("a1.Equals(a3): {0}", a1.Equals(a3))
+
    Console.WriteLine("a1.Equals(d1) : {0}", a1.Equals(d1))
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
#prompt{{
最後に、デリゲートの時はメソッド同様に呼び出すことができましたが、イベントの場合は異なります。 40行目にあるように、 RaiseEventステートメントでイベントを発生させます。 このとき、WaitOverに指定されている全てのイベントハンドラが呼び出されます。
+
2.0.50727.1433
 

        

        
~
1 == a2 : False
この例ではイベントハンドラを一つしか設定しませんでしたが、一つのイベントに対して複数のイベントハンドラを設定することも可能です。 また、イベントも同様に複数定義できます。 この次では、イベントに引数を持たせることでクラス内の情報を詳細に伝えることを考えてみます。
+
a1 == a3 : True
+
a1.Equals(a2): False
+
a1.Equals(a3): True
+
a1.Equals(d1) : False
+
}}
 

        

        
~
*&aname(asynccall){メソッドの非同期呼び出し}; (BeginInvoke, EndInvoke)
*イベントの引数
~
デリゲートを使ってメソッドを呼び出す場合、特殊なメソッドである&msdn(netfx,id,2e08f6yc){BeginInvokeメソッドとEndInvokeメソッド};を使うことで非同期的にメソッドを呼び出すことが出来ます。
ただ単にイベントハンドラを呼び出しただけでは、そのイベントが発生したときのクラス内の状態を知ることができません。 そのため、イベントに引数を持たせることを考えてみます。
 

        

        
~
BeginInvokeメソッドは、呼び出すと非同期的にメソッドの実行を開始したのち、すぐに&msdn(netfx,type,System.IAsyncResult){IAsyncResult};を返します。 このインターフェイスには、非同期的に実行したメソッドの処理が完了しているかどうかを知るためのプロパティ&msdn(netfx,member,System.IAsyncResult.IsCompleted){IsCompleted};や、メソッドの終了を待機するための待機ハンドルを参照するプロパティ&msdn(netfx,member,System.IAsyncResult.AsyncWaitHandle){AsyncWaitHandle};などが用意されています(WaitHandleについては[[programming/netfx/fcl/System.Threading.WaitHandle]]でも解説しています)。 非同期呼び出しはEndIvokeメソッドを呼び出すことで完了し、これによって非同期的に呼び出したメソッドの戻り値を取得するできるようになります。 EndInvokeメソッドはメソッドの処理が終わっていない場合でも呼び出すことが出来、その場合は処理が完了するまで待機してから結果を返します。
VB.NETのコントロールなどのほとんどのイベントハンドラでは、イベントを発生させたオブジェクトを知るための sender と、そのイベントが発生した時点での状況などを記した e が、イベントハンドラの引数として取得できます。 具体的には sender は Object型、e は EventArgsクラスを継承した型です。
 

        

        
~
以下は、結果を計算するのに時間がかかる重い処理を行うメソッドComputeを用意し、デリゲートを使って非同期的に呼び出す例です。 また、呼び出し側では処理が完了するまでIAsyncResult.AsyncWaitHandleを使って待機するようにしています。
このサンプルでも、その慣例に従って、EventArgsクラスを継承したクラスWaitOverEventArgsをイベントの引数として渡してみることにします。 このクラスにはウェイトが完了した時刻を格納させるようにします。 また、イベントに引数を持たせるため、デリゲートも多少変更を加え、名前もWaitOverEventHandlerに変えます。
 

        

        
~
#tabpage(C#)
#code(vb,指定した時間だけウェイトする・引数付きイベント版)){{
+
#code(cs){{
+
using System;
+
using System.Threading;
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    // 一つのstring型の引数を取り、int型の値を返すメソッドのデリゲートを作成
+
    Func<string, int> p = Compute;
+

          
+
    // デリゲートを使ってメソッドの非同期呼び出しを開始する
+
    IAsyncResult ar = p.BeginInvoke("answer to life the universe and everything", null, null);
+

          
+
    // 最大0.5秒待機して、メソッドの処理が完了していなければピリオドを表示して待機を続ける
+
    while (!ar.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(0.5), false))
+
    {
+
      Console.Write(".");
+
    }
+

          
+
    // メソッドの処理が完了した場合は、非同期呼び出しを終了し、メソッドの戻り値を取得する
+
    int ret = p.EndInvoke(ar);
+

          
+
    // 戻り値を表示
+
    Console.WriteLine(ret);
+
  }
+

          
+
  // 「重い」処理を行うメソッド
+
  static int Compute(string question)
+
  {
+
    Thread.Sleep(TimeSpan.FromSeconds(7.5));
+

          
+
    return question.Length;
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
 
Imports System
Imports System
 
Imports System.Threading
Imports System.Threading
 

        

        
~
Class Sample
' デリゲート型の宣言
~
  Shared Sub Main()
Public Delegate Sub WaitOverEventHandler(ByVal sender As Object, ByVal e As WaitOverEventArgs)
~
    ' 一つのString型の引数を取り、Integer型の値を返すメソッドのデリゲートを作成

          
~
    Dim p As Func(Of String, Integer) = AddressOf Compute
Public Class Main
~

          

          
~
    ' デリゲートを使ってメソッドの非同期呼び出しを開始する
    ' アプリケーションのエントリーポイント
~
    Dim ar As IAsyncResult = p.BeginInvoke("answer to life the universe and everything", Nothing, Nothing)
    Public Shared Function Main(ByVal args() As String) As Integer
~

          

          
~
    ' 最大0.5秒待機して、メソッドの処理が完了していなければピリオドを表示して待機を続ける
        Dim e As New WaitExecuter()
~
    Do Until ar.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(0.5), False)
        Dim n As New WaitOverNotifier()
~
      Console.Write(".")

          
~
    Loop
        ' ウェイトが終了したことを通知するイベントハンドラを指定
~

          
        AddHandler e.WaitOver, AddressOf n.WaitOver
~
    ' メソッドの処理が完了した場合は、非同期呼び出しを終了し、メソッドの戻り値を取得する

          
~
    Dim ret As Integer = p.EndInvoke(ar)
        Console.WriteLine("Start: {0}", DateTime.Now)
~

          

          
~
    ' 戻り値を表示
        e.Wait(3000)
~
    Console.WriteLine(ret)

          
~
  End Sub
        Return 0
~

          

          
~
  ' 「重い」処理を行うメソッド
    End Function
+
  Shared Function Compute(ByVal question As String) As Integer
+
    Thread.Sleep(TimeSpan.FromSeconds(7.5))
 

        

        
+
    Return question.Length
+
  End Function
 
End Class
End Class
+
}}
+
#tabpage-end
 

        

        
+
#prompt{{
+
..............42
+
}}
 

        

        
~
比較のために、デリゲートを使った通常の(同期的な)呼び出しを行うコードを記述すると次のようになります。
' ウェイトが終了したときのイベント引数のためのクラス
-
Public Class WaitOverEventArgs
-

          
-
    ' EventArgsを継承
-
    Inherits EventArgs
-

          
-
    ' ウェイトが終了したときの時間
-
    Protected m_dtm As DateTime
 

        

        
~
#tabpage(C#)
    ' 読み取り専用プロパティ
~
#code(cs){{
    Public ReadOnly Property WaitOverTime() As DateTime
~
using System;
        Get
~
using System.Threading;
            Return m_dtm
~

          
        End Get
~
class Sample
    End Property
+
{
+
  static void Main()
+
  {
+
    // 一つのstring型の引数を取り、int型の値を返すメソッドのデリゲートを作成
+
    Func<string, int> p = Compute;
+

          
+
    // デリゲートを使ってメソッドの同期的な呼び出しを開始する
+
    int ret = p("answer to life the universe and everything");
+

          
+
    // 戻り値を表示
+
    Console.WriteLine(ret);
+
  }
+

          
+
  // 「重い」処理を行うメソッド
+
  static int Compute(string question)
+
  {
+
    Thread.Sleep(TimeSpan.FromSeconds(7.5));
+

          
+
    return question.Length;
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
+
Imports System.Threading
 

        

        
~
Class Sample
    ' コンストラクタ
~
  Shared Sub Main()
    Public Sub New(ByVal dtm As DateTime)
~
    ' 一つのString型の引数を取り、Integer型の値を返すメソッドのデリゲートを作成
        MyBase.New()
~
    Dim p As Func(Of String, Integer) = AddressOf Compute
        m_dtm = dtm
~

          
    End Sub
+
    ' デリゲートを使ってメソッドの同期的な呼び出しを開始する
+
    Dim ret As Integer = p("answer to life the universe and everything")
+

          
+
    ' 戻り値を表示
+
    Console.WriteLine(ret)
+
  End Sub
+

          
+
  ' 「重い」処理を行うメソッド
+
  Shared Function Compute(ByVal question As String) As Integer
+
    Thread.Sleep(TimeSpan.FromSeconds(7.5))
 

        

        
+
    Return question.Length
+
  End Function
 
End Class
End Class
+
}}
+
#tabpage-end
 

        

        
+
**非同期呼び出し完了のコールバック
+
BeginInvokeメソッドの最後の二つの引数には、非同期呼び出しが終了した際に呼び出されるコールバックメソッドを指定するデリゲート&msdn(netfx,type,System.AsyncCallback){AsyncCallback};と、コールバックメソッドに渡す任意の引数を指定出来ます。 この引数は、AsyncCallbackに渡されるIAsyncResultの&msdn(netfx,member,System.IAsyncResult.AsyncState){AsyncStateプロパティ};で参照出来ます。
 

        

        
~
次の例は、上記の例をコールバックメソッドを使うように書き換えたものです。 この例では、非同期呼び出しによって処理を開始した後、完了まで待機せずに続けて別の処理を平行して行っています。 また、EndInvokeの呼び出しと戻り値の取得・表示はコールバックメソッドの側で行っています。 この例では、非同期呼び出しを開始したデリゲートを取得するために、コールバックメソッドに渡されたIAsyncResultを&msdn(netfx,type,System.Runtime.Remoting.Messaging.AsyncResult){AsyncResultクラス (System.Runtime.Remoting.Messaging)};にキャストし、&msdn(netfx,member,System.Runtime.Remoting.Messaging.AsyncResult.AsyncDelegate){AsyncDelegateプロパティ};を参照しています。
' ウェイトを行うためのクラス
-
Public Class WaitExecuter
 

        

        
~
#tabpage(C#)
    ' ウェイトが終了したことを通知するイベント
~
#code(cs){{
    Public Event WaitOver As WaitOverEventHandler
+
using System;
+
using System.Runtime.Remoting.Messaging;
+
using System.Threading;
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    Func<string, int> p = Compute;
+

          
+
    // 非同期呼び出しを開始し、完了した際にCallbackメソッドを呼び出すようにする
+
    p.BeginInvoke("answer to life the universe and everything", Callback, null);
+

          
+
    // 以降、呼び出し側では他の処理を続ける
+
    for (int i = 0; i < 10; i++)
+
    {
+
      Console.Write("process #{0}: ", i);
+

          
+
      Thread.Sleep(TimeSpan.FromSeconds(1.0));
+

          
+
      Console.WriteLine("done");
+
    }
+
  }
+

          
+
  // 非同期呼び出しが完了した際にコールバックされるメソッド
+
  static void Callback(IAsyncResult asyncResult)
+
  {
+
    // IAsyncResultをAsyncResultに変換
+
    AsyncResult ar = asyncResult as AsyncResult;
+

          
+
    // 非同期呼び出しを行ったデリゲートを取得
+
    Func<string, int> func = ar.AsyncDelegate as Func<string, int>;
+

          
+
    // 非同期呼び出しを完了し、メソッドの戻り値を取得する
+
    int ret = func.EndInvoke(ar);
+

          
+
    // 戻り値を表示
+
    Console.WriteLine("result: {0}", ret);
+
  }
+

          
+
  // 「重い」処理を行うメソッド
+
  static int Compute(string question)
+
  {
+
    Thread.Sleep(TimeSpan.FromSeconds(7.5));
+

          
+
    return question.Length;
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
+
Imports System.Runtime.Remoting.Messaging
+
Imports System.Threading
 

        

        
~
Class Sample
    ' 指定されたミリ秒だけウェイトする
~
  Shared Sub Main()
    Public Sub Wait(ByVal millisec As Integer)
+
    Dim p As Func(Of String, Integer) = AddressOf Compute
 

        

        
~
    ' 非同期呼び出しを開始し、完了した際にCallbackメソッドを呼び出すようにする
        Thread.Sleep(millisec)
+
    p.BeginInvoke("answer to life the universe and everything", AddressOf Callback, Nothing)
 

        

        
~
    ' 以降、呼び出し側では他の処理を続ける
        ' ウェイト終了イベントを発生
~
    For i As Integer = 0 To 9
        RaiseEvent WaitOver(Me, New WaitOverEventArgs(DateTime.Now))
+
      Console.Write("process #{0}: ", i)
 

        

        
~
      Thread.Sleep(TimeSpan.FromSeconds(1.0))
    End Sub
 

        

        
~
      Console.WriteLine("done")
End Class
+
    Next
+
  End Sub
 

        

        
+
  ' 非同期呼び出しが完了した際にコールバックされるメソッド
+
  Shared Sub Callback(ByVal asyncResult As IAsyncResult)
+
    ' IAsyncResultをAsyncResultに変換
+
    Dim ar As AsyncResult = DirectCast(asyncResult, AsyncResult)
 

        

        
~
    ' 非同期呼び出しを行ったデリゲートを取得
' ウェイト終了を感知するためのクラス
~
    Dim func As Func(Of String, Integer) = DirectCast(ar.AsyncDelegate, Func(Of String, Integer))
Public Class WaitOverNotifier
 

        

        
~
    ' 非同期呼び出しを完了し、メソッドの戻り値を取得する
    ' ウェイトが終了したことを知らせる
~
    Dim ret As Integer = func.EndInvoke(ar)
    Public Sub WaitOver(ByVal sender As Object, ByVal e As WaitOverEventArgs)
 

        

        
~
    ' 戻り値を表示
        ' ウェイト終了時間を出力する
~
    Console.WriteLine("result: {0}", ret)
        Console.WriteLine("Wait over, {0}.", e.WaitOverTime)
+
  End Sub
 

        

        
~
  ' 「重い」処理を行うメソッド
    End Sub
+
  Shared Function Compute(ByVal question As String) As Integer
+
    Thread.Sleep(TimeSpan.FromSeconds(7.5))
 

        

        
+
    Return question.Length
+
  End Function
 
End Class
End Class
 
}}
}}
+
#tabpage-end
 

        

        
~
#prompt{{
#prompt(実行結果){{
~
process #0: done
Start: 2003/02/21 3:51:39
~
process #1: done
Wait over, 2003/02/21 3:51:42.
~
process #2: done
Press any key to continue
+
process #3: done
+
process #4: done
+
process #5: done
+
process #6: done
+
process #7: result: 42
+
done
+
process #8: done
+
process #9: done
 
}}
}}
 

        

        
~
**非同期処理に関するドキュメント
まず、イベント引数のためのクラスWaitOverEventArgsが新しく定義されました。 このクラスはイベントが発生した時間を保持する以外の役割はありません。 64行目のRaiseEventステートメントでは、WaitOverイベントのデリゲート型が引数をとるように変わったので、それに伴い二つの引数を渡しています。 ここで、新しく作成されたWaitOverEventArgsを渡すことで、イベントが発生した時刻を知らせるわけです。 また、senderにMeを渡すことで、イベントを発生させた張本人である自分自身をイベントハンドラに通知します。
+
非同期処理およびBeginInvoke, EndInvoke, IAsyncResultについてより詳しく知るためには、以下のドキュメントを参照してください。
+
-&msdn(netfx,id,ms228969){非同期プログラミングのデザイン パターン};
+
--&msdn(netfx,id,ms228963){非同期プログラミングの概要};
+
--&msdn(netfx,id,ms228975){IAsyncResult を使用した非同期メソッドの呼び出し};
+
--&msdn(netfx,id,22t547yb){デリゲートを使用した非同期プログラミング};
 

        

        
~
また、.NET Framework 4以降では、&msdn(netfx,ns,System.Threading.Tasks){System.Threading.Tasks名前空間};のクラス群を使うことで非同期処理をより簡単に実装するとができるようになっています。 詳しくは&msdn(netfx,id,dd460693){.NET Framework の並列プログラミング};などを参照してください。
さらに、イベントハンドラの方も変更を加えます。 75行目のWaitOverイベントハンドラも、デリゲート型のパラメータ形式が変わったのでそれに伴い形式を変更します。 ここで、このイベントハンドラが呼び出されたとき、引数として渡されるeのWaitOverTimeプロパティにはこのイベントが発生した時点での時刻が格納されているので、これを表示することでウェイト終了時刻を表示してやります。
 

        

        
~
#navi(..)
このように、ほとんど同じ結果が得られるにも関わらず、イベントを使う前の一番最初のコードと、イベントを定義した最後のコードでは、全く違うプログラムかのように見えるくらい両者は異なったものになっています。 しかし、デリゲートやイベントはクラスの状態変化を通知したりするのに非常に強力なものになるので、ぜひマスターするといいでしょう。
 

        

        

programming/netfx/delegate/index.wiki.txt

current previous
1,15 1,391
~
${smdncms:title,デリゲート}
${smdncms:title,イベントとデリゲート}
~
${smdncms:keywords,デリゲート,Delegate}
${smdncms:keywords,イベント,デリゲート,Delegate,Event,EventHandler,EventArgs,AddHandler}
 
${smdncms:meta,toc-amazonlivelink-keyword,books-jp,.net framework}
${smdncms:meta,toc-amazonlivelink-keyword,books-jp,.net framework}
-
Visual Basicではコマンドボタンがクリックされたときとか、テキストボックスに文字が入力されたときなどにイベントとしてメッセージが通知されてきました。 Visual Basic .NETでも同様のイベントというものが存在します。 また、Visual Basicでのクラス同様、独自のイベントを定義することができます。
 

        

        
~
デリゲートとはC++などに存在する関数ポインタに近い機能を持つもので、GUIのイベントハンドラ、クラスやメソッドからのコールバックなどを実現するための機構として存在しています。 デリゲートを用いることで、ラムダ式や匿名メソッドなども記述することができるようになっています。
しかしVisual Basic .NETになってからイベントにまつわる部分が大きく変わりました。 個人的には.NETのイベントの方がわかりやすくなったかと思います。 ここではこのイベントと、それと切っても切り離せない関係にあるデリゲートについて考えてみたいと思います。
 

        

        
~
ここでは、デリゲートの使い方・動作などの基本的な事について、またイベントとの関わりについて解説します。
#googleadunit
 

        

        
~
-ページ一覧
*導入
~
#ls2_1(noroot,pathsort)
まず、イベント・デリゲートをいきなり使う前に、これからイベントとデリゲートの説明に使うサンプルコードを提示します。
+
-関連するページ
+
--[[programming/netfx/collections]]
+
---[[programming/netfx/collections/2_generic]]
 

        

        
-
#code(vb,指定した時間だけウェイトする){{
-
Imports System
-
Imports System.Threading
-

          
-
Module Main
-

          
-
    ' アプリケーションのエントリーポイント
-
    Public Function Main(ByVal args() As String) As Integer
-

          
-
        Console.WriteLine("Start: {0}", DateTime.Now)
-

          
-
        Wait(3000)
-

          
-
        Console.WriteLine("Finished: {0}", DateTime.Now)
-

          
-
        Return 0
-

          
-
    End Function
-

          
-
    ' 指定されたミリ秒だけウェイトする
-
    Public Sub Wait(ByVal millisec As Integer)
-

          
-
        Thread.Sleep(millisec)
-

          
-
        WaitOver()
-

          
-
    End Sub
-

          
-
    ' ウェイトが終了したことを知らせる
-
    Public Sub WaitOver()
-

          
-
        Console.WriteLine("Wait over.")
-

          
-
    End Sub
-

          
-
End Module
-
}}
-

          
-
#prompt(実行結果){{
-
Start: 2003/02/21 0:00:39
-
Wait over.
-
Finished: 2003/02/21 0:00:42
-
Press any key to continue
-
}}
-

          
-
ソースコードと実行結果とを見比べてもらえばすぐにわかりますが、複雑なことは何もやっていません。 63行目のThread.Sleep()は指定されたミリ秒だけ現在のスレッドを停止するというメソッドです。 Main()メソッドではWait()メソッドを呼び出す前後に、現在の時刻を表示するようにしています。 また、このサンプルでは3000ミリ秒、つまり3秒の間ウェイトするようにしています。 実行結果を見ればわかるように、始まりと終わりの間がちゃんと3秒になっています。
-

          
-
つぎに、このソースコードをデリゲートを使ったものに書き換えることにします。
-

          
-
*デリゲート
-
まず最初にデリゲートについて考えてみることにします。 まず、この文章を読んで勉強しようと思われる方の多くは、「イベントは知っているけどデリゲートなんて聞いたことがない」というような方だと思います。 当初僕自身も、デリゲートが理解不能でした。 C++を勉強した方の中には「関数ポインタ」というものをご存じの方もいると思いますが、デリゲートの役割はこの関数ポインタに似ているものといえます(厳密には異なるものです)。
-

          
-
というのも、デリゲートはあるメソッド(共有メソッド及びインスタンスメソッド)を特定する役割を持っているためです。 つまりその役割は、直接特定のメソッドを呼び出すのではなく、デリゲートによってそのデリゲートに指定されているメソッドを間接的に呼び出すことができるわけです。 イベントを発生するときに呼び出すべきメソッドをするためにデリゲートが使われます。 言い換えると、イベントはデリゲートに指定されているメソッドを呼び出します。
-

          
-
ここではまずイベントを用いず、先ほどのサンプルをデリゲートだけを用いたサンプルに作り替えてみます。
-

          
-
#code(vb,指定した時間だけウェイトする・デリゲート版){{
-
Imports System
-
Imports System.Threading
-

          
-
Module Main
-

          
-
    Public Delegate Sub WaitOverDelegate()
-

          
-
    Dim WaitOverMethod As WaitOverDelegate
-

          
-
    ' アプリケーションのエントリーポイント
-
    Public Function Main(ByVal args() As String) As Integer
-

          
-
        Console.WriteLine("Start: {0}", DateTime.Now)
-

          
-
        'WaitOverMethod = AddressOf WaitOver
-
        WaitOverMethod = New WaitOverDelegate(AddressOf WaitOver)
-

          
-
        Wait(3000)
-

          
-
        Console.WriteLine("Finished: {0}", DateTime.Now)
-

          
-
        Return 0
-

          
-
    End Function
-

          
-
    ' 指定されたミリ秒だけウェイトする
-
    Public Sub Wait(ByVal millisec As Integer)
-

          
-
        Thread.Sleep(millisec)
-

          
-
        WaitOverMethod()
-

          
-
    End Sub
-

          
-
    ' ウェイトが終了したことを知らせる
-
    Public Sub WaitOver()
-

          
-
        Console.WriteLine("Wait over.")
-

          
-
    End Sub
-

          
-
End Module
-
}}
-

          
-
#prompt(実行結果){{
-
Start: 2003/02/21 0:00:39
-
Wait over.
-
Finished: 2003/02/21 0:00:42
-
Press any key to continue
-
}}
-

          
-
早速デリゲートが出てきましたが、落ち着いてみてみましょう。 このサンプルではWaitOverメソッドをデリゲートにより呼び出すことを試みています。 まず、6行目では新しいデリゲート型の宣言を行っています。 デリゲートではどんなメソッドでも呼び出せるわけではなく、引数・戻り値が一致するメソッドだけを呼び出せるので、そのデリゲートがどのようなメソッドを呼び出せるかを定義します。 ここでは、戻り値無し、引数無しのメソッドを WaitOverDelegateとしています。
-

          
-
続いて8行目はたった今宣言したデリゲート型のインスタンスを格納するために、WaitOverDelegate型のオブジェクト変数 WaitOverMethodを宣言しています。 WaitOverDelegate型とおなじ形式のメソッドを呼び出すためにはこの変数に呼び出したいメソッドを指定します。 実際に指定しているのが15・16行目です。 コメント化されている部分は、このコードでも意味は変わらないということを示しているので、どちらか好きな方でコーディングすることができます。 このコードのようにデリゲート型の変数に呼び出させたいメソッドを指定するには、「AddressOf (対象のメソッド)」という風に指定してやります。 ここで、対象のメソッドは、引数・戻り値がデリゲート型で指定されているものと一致する共有メソッドまたはアクセス権のあるインスタンスメソッドです。
-

          
-
さて、デリゲート型の変数に呼び出させたいメソッドを指定することができたら、後は呼び出すだけです。 実際に呼び出しを行っているのが29行目です。 一見WaitOverMethod()というメソッドを呼び出しているように見えますが、実際にはデリゲート型変数 WaitOverMethod()に指定されているメソッドであるWaitOver()が呼び出されています。 ですから、直接WaitOver()メソッドを呼び出すようなコードは記述されていなくても、デリゲートが間接的に呼び出してくれるわけです。
-

          
-
このサンプルをさらに改変して、インスタンスのメソッドも呼び出せるかどうかを実際に見てみます。
-

          
-
*デリゲートでインスタンスメソッドを呼び出す
-
前のサンプルをクラス化して改変を加えたものが次のソースコードです。 ざっとコードの概要を説明しますと、今回使用しているデリゲート型WaitOverDelegateは、各クラス共通に用いられるのでPublicとし、クラス外で宣言します。
-

          
-
さらに、機能別に三つのクラスに分割しました。 MainクラスはアプリケーションのエントリーポイントとしてのMainメソッドを提供し、プログラムの流れを記述します。 WaitExecuterクラスはウェイトを実行するためのクラスで、ウェイトが完了するとWaitOverMethodで指定されているメソッドを呼び出します。 WaitOverNotifierクラスは、ウェイトが終了したことを通知するためのクラスです。
-

          
-
#code(vb,指定した時間だけウェイトする・クラス化版){{
-
Imports System
-
Imports System.Threading
-

          
-
' デリゲート型の宣言
-
Public Delegate Sub WaitOverDelegate()
-

          
-
Public Class Main
-

          
-
    ' アプリケーションのエントリーポイント
-
    Public Shared Function Main(ByVal args() As String) As Integer
-

          
-
        Dim e As New WaitExecuter()
-
        Dim n As New WaitOverNotifier()
-

          
-
        ' ウェイトが終了したことを通知するインスタンスメソッドを指定
-
        e.WaitOverMethod = AddressOf n.WaitOver
-

          
-
        Console.WriteLine("Start: {0}", DateTime.Now)
-

          
-
        e.Wait(3000)
-

          
-
        Console.WriteLine("Finished: {0}", DateTime.Now)
-

          
-
        Return 0
-

          
-
    End Function
-

          
-
End Class
-

          
-
' ウェイトを行うためのクラス
-
Public Class WaitExecuter
-

          
-
    Public WaitOverMethod As WaitOverDelegate
-

          
-
    ' 指定されたミリ秒だけウェイトする
-
    Public Sub Wait(ByVal millisec As Integer)
-

          
-
        Thread.Sleep(millisec)
-

          
-
        ' ウェイト終了を通知
-
        WaitOverMethod()
-

          
-
    End Sub
-

          
-
End Class
-

          
-
' ウェイト終了を感知するためのクラス
-
Public Class WaitOverNotifier
-

          
-
    ' ウェイトが終了したことを知らせる
-
    Public Sub WaitOver()
-

          
-
        Console.WriteLine("Wait over.")
-

          
-
    End Sub
-

          
-
End Class
-
}}
-

          
-
#prompt(実行結果){{
-
Start: 2003/02/21 2:40:06
-
Wait over.
-
Finished: 2003/02/21 2:40:09
-
Press any key to continue
-
}}
-

          
-
見ての通り実行結果は予想通りで、インスタンスメソッドでもデリゲートはちゃんと機能します。 参考までに、デリゲートは異なるインスタンスのメソッドを区別します。 つまり、インスタンスAとインスタンスBのメソッドMは別のものとして扱われます。
-

          
-
次はイベントを定義することにします。
-

          
-
*イベント
-
さて、ここでやっとなじみ深いイベントが出てくるわけです。 イベントはクラス内の状態の変化などを外部に伝える役割を持っています。 ところで、デリゲートはメソッドを特定する役割を持つとしました。 イベントは外部に情報を伝えるためにありますが、伝えるための各々のメソッド、つまりイベントハンドラはどんなメソッドでもいいわけではなく、引数や戻り値がすべて一致している必要があります。 ここで、イベントハンドラの型を定義するためにデリゲート型を用います。
-

          
-
説明だけではよくわからないと思うので、実際にイベントとイベントハンドラを用いたサンプルを次に示します。 例によって、前のサンプルを少し改変したものです。
-

          
-
#code(vb,指定した時間だけウェイトする・イベント版){{
-
Imports System
-
Imports System.Threading
-

          
-
' デリゲート型の宣言
-
Public Delegate Sub WaitOverDelegate()
-

          
-
Public Class Main
-

          
-
    ' アプリケーションのエントリーポイント
-
    Public Shared Function Main(ByVal args() As String) As Integer
-

          
-
        Dim e As New WaitExecuter()
-
        Dim n As New WaitOverNotifier()
-

          
-
        ' ウェイトが終了したことを通知するイベントハンドラを指定
-
        AddHandler e.WaitOver, AddressOf n.WaitOver
-

          
-
        Console.WriteLine("Start: {0}", DateTime.Now)
-

          
-
        e.Wait(3000)
-

          
-
        Console.WriteLine("Finished: {0}", DateTime.Now)
-

          
-
        Return 0
-

          
-
    End Function
-

          
-
End Class
-

          
-
' ウェイトを行うためのクラス
-
Public Class WaitExecuter
-

          
-
    ' ウェイトが終了したことを通知するイベント
-
    Public Event WaitOver As WaitOverDelegate
-

          
-
    ' 指定されたミリ秒だけウェイトする
-
    Public Sub Wait(ByVal millisec As Integer)
-

          
-
        Thread.Sleep(millisec)
-

          
-
        ' ウェイト終了イベントを発生
-
        RaiseEvent WaitOver()
-

          
-
    End Sub
-

          
-
End Class
-

          
-
' ウェイト終了を感知するためのクラス
-
Public Class WaitOverNotifier
-

          
-
    ' ウェイトが終了したことを知らせる
-
    Public Sub WaitOver()
-

          
-
        Console.WriteLine("Wait over.")
-

          
-
    End Sub
-

          
-
End Class
-
}}
-

          
-
#prompt(実行結果){{
-
Start: 2003/02/21 3:05:40
-
Wait over.
-
Finished: 2003/02/21 3:05:43
-
Press any key to continue
-
}}
-

          
-
まず、このコードで大きく変わったところから説明していきます。 まず、32行目ですが、以前はデリゲート型のパブリック変数が宣言されていましたが、ここではEventキーワードによりイベントとして宣言されています。 また、「Event (イベント名) As (デリゲート型)」とすることで、このイベントのハンドラはデリゲート型で指定されている形式と同じ形式を持たなければならなくなります。
-

          
-
イベント宣言へと変わったことで、16行目も変わりました。 AddHandlerステートメントは、イベントに適切なイベントハンドラを追加するためのものです。 参考までに、イベントのリストから削除するためにRemoveEventステートメントも用意されています。  AddHandlerステートメントは「AddHandler (イベント), AddressOf (イベントハンドラ)」のように記述します。 これにより16行目では、e.WaitOverイベントにn.WaitOver()をイベントハンドラとして追加することができます。
-

          
-
最後に、デリゲートの時はメソッド同様に呼び出すことができましたが、イベントの場合は異なります。 40行目にあるように、 RaiseEventステートメントでイベントを発生させます。 このとき、WaitOverに指定されている全てのイベントハンドラが呼び出されます。
-

          
-
この例ではイベントハンドラを一つしか設定しませんでしたが、一つのイベントに対して複数のイベントハンドラを設定することも可能です。 また、イベントも同様に複数定義できます。 この次では、イベントに引数を持たせることでクラス内の情報を詳細に伝えることを考えてみます。
-

          
-
*イベントの引数
-
ただ単にイベントハンドラを呼び出しただけでは、そのイベントが発生したときのクラス内の状態を知ることができません。 そのため、イベントに引数を持たせることを考えてみます。
-

          
-
VB.NETのコントロールなどのほとんどのイベントハンドラでは、イベントを発生させたオブジェクトを知るための sender と、そのイベントが発生した時点での状況などを記した e が、イベントハンドラの引数として取得できます。 具体的には sender は Object型、e は EventArgsクラスを継承した型です。
-

          
-
このサンプルでも、その慣例に従って、EventArgsクラスを継承したクラスWaitOverEventArgsをイベントの引数として渡してみることにします。 このクラスにはウェイトが完了した時刻を格納させるようにします。 また、イベントに引数を持たせるため、デリゲートも多少変更を加え、名前もWaitOverEventHandlerに変えます。
-

          
-
#code(vb,指定した時間だけウェイトする・引数付きイベント版)){{
-
Imports System
-
Imports System.Threading
-

          
-
' デリゲート型の宣言
-
Public Delegate Sub WaitOverEventHandler(ByVal sender As Object, ByVal e As WaitOverEventArgs)
-

          
-
Public Class Main
-

          
-
    ' アプリケーションのエントリーポイント
-
    Public Shared Function Main(ByVal args() As String) As Integer
-

          
-
        Dim e As New WaitExecuter()
-
        Dim n As New WaitOverNotifier()
-

          
-
        ' ウェイトが終了したことを通知するイベントハンドラを指定
-
        AddHandler e.WaitOver, AddressOf n.WaitOver
-

          
-
        Console.WriteLine("Start: {0}", DateTime.Now)
-

          
-
        e.Wait(3000)
-

          
-
        Return 0
-

          
-
    End Function
-

          
-
End Class
-

          
-

          
-
' ウェイトが終了したときのイベント引数のためのクラス
-
Public Class WaitOverEventArgs
-

          
-
    ' EventArgsを継承
-
    Inherits EventArgs
-

          
-
    ' ウェイトが終了したときの時間
-
    Protected m_dtm As DateTime
-

          
-
    ' 読み取り専用プロパティ
-
    Public ReadOnly Property WaitOverTime() As DateTime
-
        Get
-
            Return m_dtm
-
        End Get
-
    End Property
-

          
-
    ' コンストラクタ
-
    Public Sub New(ByVal dtm As DateTime)
-
        MyBase.New()
-
        m_dtm = dtm
-
    End Sub
-

          
-
End Class
-

          
-

          
-
' ウェイトを行うためのクラス
-
Public Class WaitExecuter
-

          
-
    ' ウェイトが終了したことを通知するイベント
-
    Public Event WaitOver As WaitOverEventHandler
-

          
-
    ' 指定されたミリ秒だけウェイトする
-
    Public Sub Wait(ByVal millisec As Integer)
-

          
-
        Thread.Sleep(millisec)
-

          
-
        ' ウェイト終了イベントを発生
-
        RaiseEvent WaitOver(Me, New WaitOverEventArgs(DateTime.Now))
-

          
-
    End Sub
-

          
-
End Class
-

          
-

          
-
' ウェイト終了を感知するためのクラス
-
Public Class WaitOverNotifier
-

          
-
    ' ウェイトが終了したことを知らせる
-
    Public Sub WaitOver(ByVal sender As Object, ByVal e As WaitOverEventArgs)
-

          
-
        ' ウェイト終了時間を出力する
-
        Console.WriteLine("Wait over, {0}.", e.WaitOverTime)
-

          
-
    End Sub
-

          
-
End Class
-
}}
-

          
-
#prompt(実行結果){{
-
Start: 2003/02/21 3:51:39
-
Wait over, 2003/02/21 3:51:42.
-
Press any key to continue
-
}}
-

          
-
まず、イベント引数のためのクラスWaitOverEventArgsが新しく定義されました。 このクラスはイベントが発生した時間を保持する以外の役割はありません。 64行目のRaiseEventステートメントでは、WaitOverイベントのデリゲート型が引数をとるように変わったので、それに伴い二つの引数を渡しています。 ここで、新しく作成されたWaitOverEventArgsを渡すことで、イベントが発生した時刻を知らせるわけです。 また、senderにMeを渡すことで、イベントを発生させた張本人である自分自身をイベントハンドラに通知します。
-

          
-
さらに、イベントハンドラの方も変更を加えます。 75行目のWaitOverイベントハンドラも、デリゲート型のパラメータ形式が変わったのでそれに伴い形式を変更します。 ここで、このイベントハンドラが呼び出されたとき、引数として渡されるeのWaitOverTimeプロパティにはこのイベントが発生した時点での時刻が格納されているので、これを表示することでウェイト終了時刻を表示してやります。
-

          
-
このように、ほとんど同じ結果が得られるにも関わらず、イベントを使う前の一番最初のコードと、イベントを定義した最後のコードでは、全く違うプログラムかのように見えるくらい両者は異なったものになっています。 しかし、デリゲートやイベントはクラスの状態変化を通知したりするのに非常に強力なものになるので、ぜひマスターするといいでしょう。