デリゲート (プログラミング)

出典: フリー百科事典『ウィキペディア(Wikipedia)』
移動先: 案内検索

デリゲート (delegate、デレゲート) とは、C#Visual Basic .NETなどの、.NET Frameworkプログラミング言語にある機能である。

概要[編集]

デリゲートは、オブジェクトへの参照と関数オブジェクトへの参照をペアにして持つものである。オブジェクト指向プログラミングとしては、メソッドカプセル化するクラスとも言える。型安全であるという特徴がある。

C++の「メンバ関数を指すポインタ」や、Object PascalDelphi)の「インスタンスのメソッドへのポインタを格納する、メソッドポインタ」と同様のものである(DelphiもC#もアンダース・ヘルスバーグによる設計である)。また、Microsoft Visual J++も、Javaと非互換のデリゲートを導入したが、.NET Frameworkのデリゲートはこれらを発展させたものである。

デリゲートは主に、イベント処理での活用(コールバック処理のカスタマイズ)を想定している。Javaなどでのインターフェイスを利用したイベント処理と比べ、メソッド名を自由に宣言できる、振る舞いをカスタマイズするためにインターフェイスの実装やスーパークラスの継承を行なう必要がない、などの利点がある。

C#では複数のデリゲートを+, -, +=, -=演算子によって結合・分離させることもできる(マルチキャストデリゲート[1])。また、デリゲートの追加・削除と呼び出しに制約を加えて安全なコードを記述するための機構としてeventキーワードが用意されている[2] [3]eventキーワードで修飾されたデリゲート型メンバーに、外部からイベントのサブスクリプション(購読)を追加・削除する手段としては、+=演算子および-=演算子によるアクセスのみが許可される。また、eventキーワードで修飾されたデリゲート型メンバーを、クラス外部からメソッドとして呼び出すことはできない。なお、+=演算子および-=演算子の処理をカスタマイズするために、eventキーワードで修飾されたデリゲート型メンバーのaddアクセッサーとremoveアクセッサーを明示的に記述することも可能である。

そのほか、スレッドプールを利用して非同期でデリゲートを実行する機能も存在する(非同期デリゲート[4])。

Visual J++[編集]

参考[編集]

C#[編集]

C#では、次の宣言文によって「string型の引数を1つと、int型の戻り値を持つデリゲート」を宣言する。

delegate int SomeDelegate(string p);

そして、次のようにデリゲート型オブジェクトを生成することができる。

SomeDelegate del = new SomeDelegate(SomeMethod);
SomeDelegate del = new SomeDelegate(delegate(string p) {
  return p.Length;
});

前者の宣言では既存のSomeMethodメソッドをデリゲートの中身として指定しており、後者の宣言では匿名メソッドによってデリゲートの中身も同時に定義している(この書き方では、return文によって返される値がSomeDelegateデリゲートの戻り値に暗黙的に変換できない場合、エラーとなる)。

なおC# 3.0以降では、匿名メソッドの代わりにラムダ式を使って下記のように記述することもできる。

SomeDelegate del = (p) => {
  return p.Length;
};

こうして生成したデリゲート型オブジェクトは、通常のメソッドのように直接実行することができる。

int ret = del("some string");

しかし、デリゲートの真価が発揮されるのはイベントと併用した時である。イベントは、次のように宣言する。

class MyClass
{
  public event SomeDelegate SomeEvent;
}

こうして宣言したイベントには、+= 演算子と -= 演算子によって、デリゲート(イベントハンドラ)を追加したり削除したりすることができる。

MyClass obj = new MyClass();
obj.SomeEvent += new SomeDelegate(SomeEventHandler1);
obj.SomeEvent -= new SomeDelegate(SomeEventHandler1);
//obj.SomeEvent = new SomeDelegate(SomeEventHandler1); // 外部からの再代入はできないのでコンパイルエラー。
//obj.SomeEvent(); // 外部からの呼び出しはできないのでコンパイルエラー。

イベントハンドラの追加が += 演算子によって行われることから推測できるように、1つのイベントには複数のイベントハンドラを登録することができる。

次のようにしてイベントの定義されたクラスの内部からイベントを起こすと、登録したイベントハンドラがまとめて実行される。

class MyClass
{
  void ExecuteEventHandlers()
  {
    if (SomeEvent != null)
      SomeEvent("some string");
  }
}

実行される順番は登録順とは関係なく、未定義である。

なお、eventで修飾されていない通常のデリゲート型メンバーと異なり、SomeEventにはクラス外部から=演算子で直接再代入をすることはできない。また、SomeEventが定義されたクラス内からしか呼び出せない。この制約により、誤ってイベントサブスクリプションをクリアしてしまうことを防止したり、イベントハンドラーを呼び出す責任の所在を明確にしたりすることができる。

P/Invokeとデリゲート[編集]

プラットフォーム呼び出し(P/Invoke)の際は、デリゲートは既定でアンマネージ(ネイティブ)の関数ポインタにマーシャリングされる[5]

例えば下記のようなC言語関数がMyLibrary.DLLに実装されているとする。

typedef BOOL (__stdcall*TMyCallbackFuncPtr)(LPCWSTR fileName);

/* カレントディレクトリのファイルを列挙する関数。ファイルが見つかるごとにコールバック関数が呼ばれる。 */
extern __declspec(dllexport) BOOL EnumFiles(TMyCallbackFuncPtr callback);

対応するP/Invokeラッパーインターフェイスおよび呼び出しの一例は下記のようになる。

using System;
using System.Runtime.InteropServices;

static class MyPInvoker
{
    public delegate bool MyCallbackDelegate([In, MarshalAs(UnmanagedType.LPWStr)]string fileName);

    [DllImport("MyLibrary.dll")]
    public extern static bool EnumFiles(MyCallbackDelegate callback);

    private static bool MyCallbackMethod(string fileName)
    {
        Console.WriteLine(fileName);
        return true; // 列挙を続行。
    }

    private static void Test()
    {
        EnumFiles(MyCallbackMethod);
    }
}

P/Invokeでデリゲートを渡す場合は、デリゲート型のインスタンスがガベージコレクションにより回収されてしまわないように注意する必要がある。また、コールバック関数の呼び出し規約が一致するようにしなければならない(.NET 1.1まではstdcall呼び出し規約のみが使用可能だったが、.NET 2.0以降ではUnmanagedFunctionPointerAttribute属性を明示的に指定することでcdecl呼び出し規約を使用することも可能である[6])。

その他の言語のデリゲート[編集]

D言語には関数オブジェクトがあり、型としてfunctionとdelegateがある。無名関数を作る式として関数リテラルがあり、functionとdelegateのそれぞれに対応した構文がある。関数リテラルの省略構文としてラムダ式がある。functionとdelegateの違いは、作られたスコープの環境にアクセスできるかどうかで、アクセスする場合はデリゲートである必要がある。ラムダ式では内容に応じて、デリゲートである必要がある場合はデリゲートになる。

Objective-Cは言語機能としてデリゲートを持たないが、Objective-Cを用いたイベント駆動型ソフトウェアを開発する際の基本的なデザインパターンとして「委譲」が採用されている[7]

脚注[編集]

[ヘルプ]

関連項目[編集]