C# Programming

Image

デリゲート

開発環境: Visual Studio 2003

デリゲートって何?

Cでいうところの関数ポインタをタイプ セーフに拡張したものと考えられます。マルチデリゲーションも可能になっているので、関数ポインタとまったく同じと考えてはいけません。

どうしてデリゲートが必要なの?

例として Windows.Forms のボタンを押したら、テキストボックスに文字列を表示する例を考えて見ましょう。もし、デリゲートが無いとしたら、どうなるでしょう。ボタンの開発者(マイクロソフト)はボタンが押された時に、どうやってアプリケーション開発者(あなた)のコードに制御を渡したらいいでしょうか。直接知らせる手段がないとなると、どこかにボタンが押されたという状態をストアして、あなたにそれを監視してもらうしかありません。そうすると、アプリケーション開発者(あなた)は常にボタンの状態を監視していなければならず、非効率です。そこで、ボタンが押されたときに、アプリケーション開発者(あなた)が書いたメソッドを呼んでもらうようにすれば簡単になります。そのために、Cでいうところの関数ポインタが必要になるのです。これは、相手にメソッドを登録しておいて、呼び返してもらうのでコールバックとも呼ばれています。

どうして、関数ポインタではなく、デリゲートなのか。

C#はタイプセーフをひとつの特徴にしていますし、これが主流の考え方ですね。このために、C#でも同様に C, C++ のポインタを排除しています。(Unsafe, IntPtr のようなとんでもないものがありますが、それはさておき)C, C++ の関数を指し示すポインタでは、ポインタ自体が数値なのでこれを書き換えることができてしまいます。そのようなタイプアンセーフを避けるために、タイプセーフな delegate void Method( args ) というデリゲート型を導入しているのです。

イベントハンドラーって、デリゲートっていうのは本当?

では、ボタンを押されたときの C# のコードをみてみましょう。まず、フォームデザイナーでボタンを1つフォームに貼り付けて、次にボタンをダブルクリックして、イベントハンドラーを追加してください。ソースコードは次のようになっているはずです。
#region Windows Form Designer generated code
/// <summary>
/// デザイナ サポートに必要なメソッドです。このメソッドの内容を
/// コード エディタで変更しないでください。
/// </summary>
private void InitializeComponent()
{
・・・中略
this.button1.Click += new System.EventHandler(this.button1_Click);
        ・・・中略
}
#endregion

private void button1_Click(object sender, System.EventArgs e)
{
// ボタンが押されたときの処理
}
ここで、System.EventHandler は、public delegate void EventHandler( object sender, EventArgs e ); というデリゲートです。そのイベントハンドラーを、this.button1.Click += new System.EventHandler(this.button1_Click) という形で Click に追加しています。さて、この Click のシンタックスをしらべてみると、public delegate void EventHandler( object sender, EventArgs e ); です。つまり、イベントハンドラーという呼ばれ方をしていますが、要はデリゲートなのです。これにより、アプリケーション開発者(あなた)が書いたコードを、そのことを知らないボタンの開発者(マイクロソフト)が動的に関数を呼び出すことができるようになるのです。

デリゲーションの例1(引数の渡し方)

この例では、MyFunction1, MyFunction2 をデリゲートとしてそれぞれmyFunc1, myFun2 として宣言します。そして、CallDeleg メソッドの引数にそのデリゲートを渡しています。引数は、CallDeleg(myFunc d("Hello World!"のように渡します。
        delegate int Deleg(string str);

                static void TestDeleg()
                {
                        
                        Deleg myFunc1 = new Deleg(MyFunction1);
                        Deleg myFunc2 = new Deleg(MyFunction2);
                        CallDeleg(myFunc1, "Hello");
                        CallDeleg(myFunc2, "World2");
                }

                // デリゲーションを引数で渡すことができる
                // 実行時にどのデリゲーションが呼ばれるか制御できる
                static void CallDeleg(Deleg d, string str)
                {
                        int ret = d( str );
                        Console.WriteLine( " {0} characters", ret);
                }

                // デリゲーション1
                static int MyFunction1(string str)
                {
                        Console.WriteLine("MyFunction1 {0}", str);
                        return str.Length;
                }

                // デリゲーション2
                static int MyFunction2(string str)
                {
                        Console.WriteLine("MyFunction2 {0}", str);
                        return str.Length;
                }
デリゲート オブジェクトは、"+" 演算子で結合できます。結合されたデリゲートは、順番にデリゲートを呼び出されます。結合できるのは同じ型のデリゲートだけです。デリゲート型の戻り値は void 型である必要があります。"-" 演算子によりデリゲートを削除できます。
注意:
メソッドの引数型と戻り値の型は、デリゲートの引数型と戻り値の型に一致する必要があります。

デリゲーションの例2(デリゲーションの結合)

この例では、+オペレータを使って、MyFunction1, MyFunction2 を1つのデリゲートに結合しています。結合されたデリゲートは、順番にデリゲートを呼び出します。この機能は、マルチキャストイベントに使われています。
                delegate void Deleg(string str);

                static void TestDeleg()
                {
                        
                        Deleg deleg;
                        deleg = new Deleg(MyFunction1);
                        deleg += new Deleg(MyFunction2);
                        CallDeleg(deleg, "Hello World");
                }

                // デリゲーションを引数で渡すことができる
                // 実行時にどのデリゲーションが呼ばれるか制御できる
                static void CallDeleg(Deleg d, string str)
                {
                        d( str );
                }

                // デリゲーション1
                static void MyFunction1(string str)
                {
                        Console.WriteLine("MyFunction1 {0}", str);
                }

                // デリゲーション2
                static void MyFunction2(string str)
                {
                        Console.WriteLine("MyFunction2 {0}", str);
                }
注意:
結合できるのは同じ型のデリゲートだけです。
結合するデリゲート型の戻り値は void 型である必要があります。これは、複数のデリゲートが異なる戻り値を返した場合に問題となるためです。
"-" 演算子によりデリゲートを削除できます。
メソッドの引数型と戻り値の型は、デリゲートの引数型と戻り値の型に一致する必要があります。