System.Delegate と
この記事では、デリゲートをサポートする .NET のクラスと、それらのクラスを delegate
キーワードにマップする方法について説明します。
デリゲートとは
デリゲートは、オブジェクトへの参照を格納する方法と同様に、メソッドへの参照を格納する方法と考えてください。 メソッドにオブジェクトを渡すのと同様に、デリゲートを使用してメソッド参照を渡すことができます。 これは、さまざまなメソッドを "プラグイン" してさまざまな動作を提供できる柔軟なコードを記述する場合に便利です。
たとえば、2 つの数値に対して演算を実行できる電卓があるとします。 加算、減算、乗算、除算を個別のメソッドにハードコーディングする代わりに、デリゲートを使用して、2 つの数値を受け取り、結果を返す任意の演算を表すことができます。
デリゲート型を定義する
次に、 delegate
キーワードを使用してデリゲート型を作成する方法を見てみましょう。 デリゲート型を定義するときは、基本的にそのデリゲートに格納できるメソッドの種類を記述するテンプレートを作成します。
デリゲート型は、メソッド シグネチャに似た構文を使用して定義しますが、先頭に delegate
キーワードがあります。
// Define a simple delegate that can point to methods taking two integers and returning an integer
public delegate int Calculator(int x, int y);
この Calculator
デリゲートは、2 つの int
パラメーターを受け取り、 int
を返す任意のメソッドへの参照を保持できます。
より実用的な例を見てみましょう。 リストを並べ替える場合は、並べ替えアルゴリズムに項目の比較方法を指示する必要があります。 デリゲートが List.Sort()
メソッドにどのように役立つかを見てみましょう。 最初の手順では、比較操作のデリゲート型を作成します。
// From the .NET Core library
public delegate int Comparison<in T>(T left, T right);
この Comparison<T>
デリゲートは、次のメソッドへの参照を保持できます。
- 型の 2 つのパラメーターを受け取ります
T
int
を返します (通常、"より小さい"、"等しい"、または "より大きい" を示す場合は -1、0、または 1)。
このようなデリゲート型を定義すると、コンパイラによって、シグネチャに一致する System.Delegate
から派生したクラスが自動的に生成されます。 このクラスは、メソッド参照の格納と呼び出しのすべての複雑さを処理します。
Comparison
デリゲート型はジェネリック型であり、任意の型T
で動作できることを意味します。 ジェネリックの詳細については、「 ジェネリック クラスとメソッド」を参照してください。
構文は変数の宣言に似ていますが、実際には新しい型を宣言していることに注意 してください。 デリゲート型は、クラス内、名前空間内、またはグローバル名前空間内で直接定義できます。
注
グローバル名前空間でデリゲート型 (またはその他の型) を直接宣言することはお勧めしません。
コンパイラでは、この新しい型の追加ハンドラーと削除ハンドラーも生成されるため、このクラスのクライアントはインスタンスの呼び出しリストからメソッドを追加および削除できます。 コンパイラは、追加または削除されるメソッドのシグネチャが、デリゲート型の宣言時に使用されるシグネチャと一致することを強制します。
デリゲートのインスタンスを宣言する
デリゲート型を定義したら、その型のインスタンス (変数) を作成できます。 これは、メソッドへの参照を格納できる "スロット" を作成すると考えてください。
C# のすべての変数と同様に、デリゲート インスタンスを名前空間またはグローバル名前空間で直接宣言することはできません。
// Inside a class definition:
public Comparison<T> comparator;
この変数の型は Comparison<T>
(前に定義したデリゲート型) であり、変数の名前は comparator
。 この時点で、 comparator
はまだメソッドを指していません。空のスロットが埋まるのを待っているようなものです。
他の変数型と同様に、デリゲート変数をローカル変数またはメソッド パラメーターとして宣言することもできます。
デリゲートを呼び出す
メソッドを指すデリゲート インスタンスを作成したら、デリゲートを使用してそのメソッドを呼び出す (呼び出す) ことができます。 デリゲートの呼び出しリストにあるメソッドを呼び出すには、そのデリゲートをメソッドと同じように呼び出します。
Sort()
メソッドで比較デリゲートを使用してオブジェクトの順序を決定する方法を次に示します。
int result = comparator(left, right);
この行では、デリゲートにアタッチされているメソッドが 呼び出 されます。 デリゲート変数はメソッド名であるかのように扱い、通常のメソッド呼び出し構文を使用して呼び出します。
ただし、このコード行では、ターゲット メソッドがデリゲートに追加されていることを前提としています。 メソッドがアタッチされていない場合、上の行によって NullReferenceException
がスローされます。 この問題に対処するために使用されるパターンは、単純な null チェックよりも高度であり、この シリーズの後半で説明します。
呼び出しターゲットの割り当て、追加、および削除
これで、デリゲート型の定義、デリゲート インスタンスの宣言、およびデリゲートの呼び出しを行う方法について説明しました。 しかし、メソッドを実際にデリゲートに接続するにはどうすればよいですか? ここでデリゲートの割り当てが行われます。
デリゲートを使用するには、デリゲートにメソッドを割り当てる必要があります。 割り当てるメソッドには、デリゲート型が定義するのと同じシグネチャ (同じパラメーターと戻り値の型) が必要です。
実際の例を見てみましょう。 文字列のリストを長さで並べ替えたいとします。 Comparison<string>
デリゲートシグネチャに一致する比較メソッドを作成する必要があります。
private static int CompareLength(string left, string right) =>
left.Length.CompareTo(right.Length);
このメソッドは 2 つの文字列を受け取り、どの文字列が "大きい" かを示す整数を返します (この場合は長くなります)。 メソッドはプライベートとして宣言されています。これは完全に問題ありません。 メソッドをデリゲートと共に使用するために、パブリック インターフェイスの一部である必要はありません。
これで、このメソッドを List.Sort()
メソッドに渡すことができます。
phrases.Sort(CompareLength);
丸括弧を付けずにメソッド名を使用していることに注意してください。 これにより、メソッド参照を後で呼び出すことができるデリゲートに変換するようにコンパイラに指示します。 Sort()
メソッドは、2 つの文字列を比較する必要がある場合は常に、CompareLength
メソッドを呼び出します。
デリゲート変数を宣言し、その変数にメソッドを割り当てることで、より明示的にすることもできます。
Comparison<string> comparer = CompareLength;
phrases.Sort(comparer);
どちらの方法でも同じことが実現されます。 1 つ目の方法はより簡潔ですが、2 つ目の方法ではデリゲートの割り当てが明示的になります。
単純なメソッドの場合、別のメソッドを定義する代わりに ラムダ式 を使用するのが一般的です。
Comparison<string> comparer = (left, right) => left.Length.CompareTo(right.Length);
phrases.Sort(comparer);
ラムダ式は、単純なメソッドをインラインで定義するためのコンパクトな方法を提供します。 デリゲート ターゲットにラムダ式を使用する方法については、 後のセクションで詳しく説明します。
これまでの例では、1 つのターゲット メソッドを持つデリゲートを示しています。 ただし、デリゲート オブジェクトは、1 つのデリゲート オブジェクトに複数のターゲット メソッドがアタッチされている呼び出しリストをサポートできます。 この機能は、イベント処理シナリオに特に役立ちます。
デリゲート クラスと MulticastDelegate クラス
バックグラウンドで使用してきたデリゲート機能は、.NET Framework の 2 つの主要なクラス ( Delegate と MulticastDelegate) に基づいて構築されています。 通常、これらのクラスは直接操作しませんが、デリゲートを機能させる基盤を提供します。
System.Delegate
クラスとその直接サブクラスSystem.MulticastDelegate
は、デリゲートの作成、デリゲート ターゲットとしてのメソッドの登録、およびデリゲートに登録されているすべてのメソッドの呼び出しをフレームワークでサポートします。
興味深い設計の詳細を次に示します。 System.Delegate
と System.MulticastDelegate
は、それ自体が使用できるデリゲート型ではありません。 代わりに、作成するすべての特定のデリゲート型の基底クラスとして機能します。 C# 言語を使用すると、これらのクラスから直接継承できなくなります。代わりに delegate
キーワードを使用する必要があります。
delegate
キーワードを使用してデリゲート型を宣言すると、C# コンパイラによって、特定のシグネチャを持つMulticastDelegate
から派生したクラスが自動的に作成されます。
なぜこの設計?
この設計は、C# と .NET の最初のリリースに根ざしています。 設計チームにはいくつかの目標がありました。
型の安全性: チームは、デリゲートを使用するときに、言語によって型の安全性が強制されるようにしたいと考えていました。 これは、デリゲートが正しい型と引数の数で呼び出され、戻り値の型がコンパイル時に正しく検証されることを意味します。
パフォーマンス: コンパイラで特定のメソッド シグネチャを表す具象デリゲート クラスを生成することで、ランタイムはデリゲート呼び出しを最適化できます。
簡略化: デリゲートは、ジェネリックが導入される前の 1.0 .NET リリースに含まれていました。 時間の制約内で動作するために必要な設計。
解決策は、コンパイラにメソッドシグネチャに一致する具体的なデリゲートクラスを作成し、複雑さを隠しながら型の安全性を確保することでした。
デリゲート メソッドの操作
派生クラスを直接作成することはできませんが、 Delegate
クラスと MulticastDelegate
クラスで定義されたメソッドを使用することがあります。 知っておくべき最も重要なものを次に示します。
あなたが作業するすべてのデリゲートはMulticastDelegate
から派生します。 "マルチキャスト" デリゲートは、デリゲートを介して呼び出すときに、複数のメソッド ターゲットを呼び出すことができることを意味します。 元の設計では、1 つのメソッドしか呼び出しできないデリゲートと、複数のメソッドを呼び出すことができるデリゲートを区別することを検討しました。 実際には、この区別は当初の考えよりも役に立たないため、.NET のすべてのデリゲートで複数のターゲット メソッドがサポートされています。
デリゲートを操作するときに最もよく使用されるメソッドは次のとおりです。
Invoke()
: デリゲートにアタッチされているすべてのメソッドを呼び出します。BeginInvoke()
/EndInvoke()
: 非同期呼び出しパターンに使用されます (ただし、async
/await
が推奨されるようになりました)
ほとんどの場合、これらのメソッドは直接呼び出しません。 代わりに、上記の例に示すように、デリゲート変数でメソッド呼び出し構文を使用します。 ただし、 このシリーズの後半で説明するように、これらのメソッドで直接動作するパターンがあります。
概要
C# 言語の構文が基になる .NET クラスにどのようにマップされるかを確認したので、より複雑なシナリオで厳密に型指定されたデリゲートがどのように使用、作成、および呼び出されるかを調べる準備ができました。
.NET