連載
One Point .NET

デリゲート再入門

吉松 史彰
2003/07/23

デリゲートの宣言とその実体

 デリゲートは、次のように宣言する。

delegate 戻り値のデータ型 デリゲートの名前 (仮引数リスト);
C#におけるデリゲートの宣言
 
Delegate Sub デリゲートの名前 (仮引数リスト)

または

Delegate Function デリゲートの名前 (仮引数リスト) As 戻り値のデータ型
VB.NETにおけるデリゲートの宣言

 デリゲートは型の一種であるため、ソースコード中の型を宣言できる場所であればどこででも宣言できる。

 例えば次のようなデリゲートを定義したと仮定する。

delegate void ShowMessageDelegate(string msg);

 これをC#のコンパイラでコンパイルすると、現在の実装では次のようなクラスが出力される。

class ShowMessage : System.MulticastDelegate {
  public ShowMessageDelegate(object object, IntPtr method) { …… }
  public virtual IAsyncResult BeginInvoke(string msg, AsyncCallback callback, object object) { …… }
  public virtual void EndInvoke(IAsyncResult result) { …… }
  public virtual void Invoke(string msg) { …… }
}

 つまり、デリゲートの実体はSystem.MulticastDelegateクラスから継承するクラスである。また同時に、3つのメソッドと1つのコンストラクタも自動作成されるが、これらの実装はいずれも共通言語ランタイム自身が提供するため、プログラミング言語で記述することはできない。自動生成されるメソッドのうち、Invokeメソッド、BeginInvokeメソッド、およびEndInvokeメソッドのシグネチャは、デリゲートの宣言内容に合わせてコンパイル時に決定される。

デリゲートの利用方法

 デリゲートを利用するために呼び出し側が行うのは、次の2つである。まず、デリゲートは型であるため、利用するためにはデリゲート型の変数を宣言する。

ShowMessageDelegate ShowMessage; 

 あとは、適切なタイミングでデリゲートを呼び出せばよい。デリゲートを呼び出すには、Invokeメソッドを実行する。

ShowMessage.Invoke("hello, from delegate!");
C#におけるデリゲートの呼び出し(イメージ)
 
ShowMessage.Invoke("hello, from delegate!")
VB.NETにおけるデリゲートの呼び出し

 ただし、C#ではデリゲートを特別扱いしているため、実際には上記のC#のコードはコンパイルできない。C#では、「.Invoke」の記述を明示せず、あたかもデリゲート型の変数がメソッドであるかのように呼び出しを行う。

ShowMessage("hello, from delegate!"); 

 もちろんここまでの手順では、デリゲートというクッションを呼び出しているだけで、呼び出した結果何が起こるか、呼び出した側にはまったく分からない状態になっている。

メソッドのデリゲートへの登録

 デリゲートへの呼び出しに対応して実際に呼び出してほしいコードを作成する側では、実際に呼び出されるメソッドをどこかのタイミングでデリゲートに登録する必要がある。デリゲートに登録できるのは、デリゲート型と同一のシグネチャを持つメソッドだけに限られる。そのようなメソッドをまずは用意しなければならない。

void ConcreteShowMessage(string msg) { 
  Console.WriteLine(msg);
}

 実際に呼び出されるメソッドが準備できたら、デリゲートにメソッドを登録する。デリゲートは型であるため、登録するにはnew演算子でデリゲートの実体を作成しなければならない。このとき、デリゲートのコンストラクタの引数には用意したメソッドを指定する。

ShowMessage = new ShowMessageDelegate(ConcreteShowMessage); 

 現在の実装ではデリゲートはクラスに変換されるということをすでに解説したが、その解説で示したクラスには引数を2つ取るコンストラクタが定義されている。

public ShowMessageDelegate(object object, IntPtr method) { …… }

 しかし、new演算子で指定しているコンストラクタの引数は1つだけになっている。C#やVB.NETのコンパイラは、new演算子がデリゲートに対して適用されると、コンストラクタに渡されている実引数から、呼び出し対象のオブジェクト(実際の第1引数)と、メソッドを示すメタデータ・トークン(実際の第2引数)を抽出するコードに変換する*1

*1 正確には、この動作はCommon Language Infrastructure(CLI)仕様で決められているもので、コンパイラが勝手に行っているわけではない。

 これでデリゲートへの呼び出しと、実際に呼び出されるメソッドとを結びつけることができるようになる。実際に動作するコードは次のようになる。

using System;

delegate void ShowMessageDelegate(string msg);

class Callable {
  internal void ConcreteShowMessage(string msg) { 
    Console.WriteLine(msg);
  }
}

class Caller {
  internal ShowMessageDelegate ShowMessage; 

  internal void CallDelegate() {
    ShowMessage("hello, from delegate!"); 
  }
}

class App {
  static void Main() {
    Callable callable = new Callable();
    Caller caller = new Caller();

    caller.ShowMessage
      = new ShowMessageDelegate(callable.ConcreteShowMessage); 

    caller.CallDelegate();
  }
}
デリゲートを使用したサンプル・プログラム

 WindowsフォームのButtonコントロールは、上記のコードのCallerクラスに相当する。Windowsフォームでは、上記のShowMessageDelegateデリゲートの代わりに、System.EventHandlerデリゲートが定義されている。Buttonコントロールには、上記のコードのShowMessageフィールドに相当するものとしてClickという名前のデリゲート型のフィールドが宣言されている。Buttonコントロールは、上記のCallDelegateメソッドに相当する内部のコードの中で、上記のコードの「ShowMessage」の呼び出しの代わりに「Click」を呼び出しているのである*2

*2 Button.Clickは実際には単なるフィールドではなくイベントとして定義されているが、イベントとはデリゲート型のフィールドと、そのフィールドに値を設定するメソッド、そして値を削除するメソッドとを組み合わせるためのメタデータに過ぎない。デリゲートの本質とイベントにはまったく関係がない。そのため、本稿でもイベントには触れない。

 開発者があとから作成するbutton1_Clickメソッドを含んでいるForm1クラスは、上記のコードのCallableクラスに相当する。button1_Clickメソッドは、上記のコードではConcreteShowMessageメソッドに相当する。このコードは、Buttonコントロール(Callerクラス)のコンパイル後に書いてもかまわないのである。上記のコードのAppクラスのMainメソッドのコードに相当するコードは、Visual Studio .NETを利用すると、Windowsフォーム・デザイナが自動生成している。このようにして、Buttonコントロールのコンパイル時には、button1_Clickメソッドがまだ存在しないという問題を解決しているのである。


 INDEX
  連載 One Point .NET
  デリゲート再入門
    1.デリゲートの役割
  2.デリゲートの実体とその利用方法
    3.マルチキャストとメソッドの非同期実行
 
「連載 One Point .NET」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間