デリゲート (プログラミング)
デリゲート (英: delegate) とは、主にC#、Visual Basic .NETなどの、.NET環境向けのプログラミング言語(.NET言語)に用意されている機能であり、参照型の一種(デリゲート型)である。
概要
[編集]デリゲートは、オブジェクトインスタンスへの参照とメソッドへの参照をペアにしてカプセル化するものである。概念としてはC言語やC++の関数ポインタに近いが、デリゲートは完全なオブジェクト指向である[1]。型安全であるという特徴がある[2]。2002年にリリースされた.NET FrameworkではSystem.Delegate
クラス[3]が定義されており、このクラスおよび派生クラスを簡潔に利用するための各種構文が.NET言語に導入された。
Object Pascal(Delphi)の「インスタンスのメソッドへのポインタを格納する、メソッドポインタ」と同様のものである。また、Microsoft Visual J++も、Javaと非互換のデリゲートを導入したが、.NET Frameworkのデリゲートはこれらを発展させたものである[注釈 1]。
なお、C++における「クラスの非静的メンバ関数を指す関数ポインタ」は、関数呼び出し時にそのクラスのインスタンスを必要とする。デリゲートのようにオブジェクトとして扱うためにはstd::function
などの関数オブジェクトによるカプセル化が必要となる。
デリゲートにより、メソッド単位のコンポジション (合成) が可能となる。デリゲートは主に、イベント処理での活用(コールバック処理のカスタマイズ)を想定している。Javaなどでのインターフェイスを利用したイベント処理と比べ、デリゲートによって参照されるメソッド(実体)の名前を自由に宣言できる、振る舞いをカスタマイズするために(明示的な)インターフェイスの実装やスーパークラスの継承を行なう必要がない(新たにクラスをわざわざ定義しなくてよい[注釈 2])、などの利点がある。
C#では複数のデリゲートを+
, -
, +=
, -=
演算子によって結合・分離させることもできる(マルチキャストデリゲート[4])。また、デリゲートの追加・削除と呼び出しに制約を加えて安全なコードを記述するための機構としてevent
キーワードが用意されている[5] [6]。event
キーワードで修飾されたデリゲート型メンバーに、外部からイベントのサブスクリプション(購読)を追加・削除する手段としては、+=
演算子および-=
演算子によるアクセスのみが許可される。また、event
キーワードで修飾されたデリゲート型メンバーを、クラス外部からメソッドとして呼び出すことはできない。なお、+=
演算子および-=
演算子の処理をカスタマイズするために、event
キーワードで修飾されたデリゲート型メンバーのadd
アクセッサーとremove
アクセッサーを明示的に記述することも可能である。
そのほか、スレッドプールを利用して非同期でデリゲートを実行する機能も存在する(非同期デリゲート[7])。
Visual J++
[編集]この節の加筆が望まれています。 |
参考
[編集]- About Microsoft's "Delegates"
- Microsoftの「Delegate」について - ウェイバックマシン(2016年2月3日アーカイブ分)[リンク切れ]
- The Truth About Delegates
C#
[編集]C#の例を示す。まず以下では、「string
型の引数を1つと、int
型の戻り値を持つデリゲート」を宣言している。
delegate int SomeDelegate(string p);
コンパイラによって、System.Delegate
から派生するSomeDelegate
型が生成される。
次に、以下のようなメソッドを持つクラスがあったとする。
class SomeClass
{
private string someField = "TEST:";
public int SomeMethod(string p)
{
return this.someField.Length + p.Length;
}
public static int SomeStaticMethod(string p)
{
return p.Length;
}
}
そして、以下のように(デリゲートと同じ引数と戻り値を持つ)既存の名前付きメソッドを参照するデリゲート型オブジェクトを生成することができる。
SomeDelegate del = new SomeDelegate(SomeClass.SomeStaticMethod);
C/C++の関数ポインタと異なり、静的メソッドだけでなくインスタンスメソッドをデリゲートに代入することもできる(メソッドとインスタンスのペアがカプセル化される)。
SomeClass obj = new SomeClass();
SomeDelegate del = new SomeDelegate(obj.SomeMethod);
C# 2.0以降では、匿名メソッド (anonymous method) としてインラインで記述したメソッドを参照するデリゲートを同時に定義することもできる。この書き方では、return
文によって返される値がSomeDelegate
デリゲートの戻り値に暗黙的に変換できない場合、エラーとなる。
SomeDelegate del = new SomeDelegate(delegate(string p) {
return p.Length;
});
なおC# 3.0以降では、匿名メソッドの代わりにラムダ式を使って下記のように記述することもできる。
SomeDelegate del = (p) => {
return p.Length;
};
匿名メソッドとラムダ式は併せて匿名関数 (anonymous function) と呼ばれる。匿名関数は外側の変数にアクセスすることができ、これをキャプチャと呼ぶ。匿名関数とデリゲートを用いてクロージャを実現することができる。
こうして生成したデリゲート型オブジェクトは、通常のメソッドのように直接実行することができる。
int ret = del("some string");
なお、上記は以下のInvoke
メソッド呼び出しに対する糖衣構文である。Invoke
メソッドはコンパイラによって自動生成される。
int ret = del.Invoke("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)の際は、デリゲートは既定でアンマネージ(ネイティブ)の関数ポインタにマーシャリングされる[8]。
例えば下記のような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でデリゲートを渡す場合は、デリゲート型のインスタンスがガベージコレクションにより回収されてしまわないように注意する必要がある[9][10]。また、コールバック関数の呼び出し規約が一致するようにしなければならない(.NET 1.1まではstdcall呼び出し規約のみが使用可能だったが、.NET 2.0以降ではUnmanagedFunctionPointerAttribute属性を明示的に指定することでcdecl呼び出し規約を使用することも可能である[11])。
その他の言語のデリゲート
[編集]D言語には関数オブジェクトがあり、型としてfunctionとdelegateがある。無名関数を作る式として関数リテラルがあり、functionとdelegateのそれぞれに対応した構文がある。関数リテラルの省略構文としてラムダ式がある。functionとdelegateの違いは、作られたスコープの環境にアクセスできるかどうかで、アクセスする場合はデリゲートである必要がある。ラムダ式では内容に応じて、デリゲートである必要がある場合はデリゲートになる。
Javaはバージョン8にてラムダ式とともにメソッド参照の機能を導入した。ただしマルチキャストデリゲートに相当する機能はない。
C++/CLIは.NETマネージ言語であり、.NETのデリゲートをサポートする。メソッド宣言にdelegate
キーワードを使用することで、System::MulticastDelegate
を継承した、Invoke
という名前のメソッドを持つクラスが自動定義される点などは、C#とほぼ同じである[12]。ただし、C++11で追加されたラムダ式を使ってデリゲートのインスタンスを生成することはできず、gcnew
を使って明示的にデリゲートのインスタンスを生成する必要があるため、C# 2.0以降と比較してコードが煩雑になる[13]。
C++/CXは.NETマネージ言語ではなくネイティブ言語拡張だが、デリゲートをサポートする。イベントハンドラーの割り当てに利用される。ただし、C++/CXは参照カウントベースのガベージコレクションを採用していることから、強い参照による循環参照を防ぐため、イベントハンドラーの記述にはラムダ式よりも名前付き関数を利用することが推奨されている[14]。
Objective-Cは言語機能としてデリゲートを持たないが、Objective-Cを用いたイベント駆動型ソフトウェアを開発する際の基本的なデザインパターンとして「委譲」が採用されている[15]。実態はJavaのインターフェイスを利用したイベントコールバックと同じく、XxxDelegate
という名前を持つプロトコル(抽象型の一種)を採用(adopt)することでイベント処理のカスタマイズを実現する。Swiftのデリゲートも同様である。
脚注
[編集]注釈
[編集]- ^ DelphiもJ++もC#もアンダース・ヘルスバーグによる設計である。
- ^ Javaではまず
interface
構文によるインターフェイスやclass
構文によるスーパークラスの定義が必要であり、コード量が膨れ上がりやすい。サブクラス(インターフェイス実装クラス)の記述に関しては、無名クラスやラムダ式を利用することで簡略化できるが、コンパイラによって新たにサブクラス(インターフェイス実装クラス)が定義されることに変わりはない。
出典
[編集]- ^ デリゲート - C# プログラミング ガイド | Microsoft Docs
- ^ delegate - C# リファレンス | Microsoft Docs
- ^ Delegate Class (System) | Microsoft Learn
- ^ 方法 : デリゲートを結合する (マルチキャスト デリゲート) (C# プログラミング ガイド)
- ^ event (C# リファレンス)
- ^ 方法 : イベント サブスクリプションとサブスクリプションの解除 (C# プログラミング ガイド)
- ^ デリゲートを使用した非同期プログラミング
- ^ デリゲートに対する既定のマーシャリング
- ^ コールバック メソッドとしてのデリゲートのマーシャ リング | Microsoft Docs
- ^ 既定のマーシャリングの動作 | Microsoft Docs
- ^ C#からコールバック関数を使うCの関数を呼ぶ | sgryjp.log
- ^ delegate (C++/CLI and C++/CX) | Microsoft Learn
- ^ How to: Define and Use Delegates (C++/CLI) | Microsoft Learn
- ^ Delegates (C++/CX) | Microsoft Docs
- ^ Delegates and Data Sources | Apple Developer | Documentation Archive