System.Delegate と delegate
キーワード
この記事では、デリゲートをサポートする .NET のクラスと、それが delegate
キーワードにどのように対応付けられるかについて取り上げます。
デリゲート型を定義する
最初に、'delegate' キーワードから始めましょう。これは、主に、デリゲートを操作する際に使用するためです。 delegate
キーワードを使用したときにコンパイラで生成されるコードは、Delegate クラスおよび MulticastDelegate クラスのメンバーを呼び出すメソッド呼び出しにマップされます。
デリゲート型を定義するには、メソッド シグネチャの定義と同様の構文を使用します。 定義に delegate
キーワードを追加するだけです。
続けて、例として List.Sort() メソッドを使用してみましょう。 最初の手順では、比較デリゲートの型を作成します。
// From the .NET Core library
// Define the delegate type:
public delegate int Comparison<in T>(T left, T right);
コンパイラは、使用されたシグネチャ (ここでは、整数を返し、2 つの引数を持つメソッド) と一致する System.Delegate
から派生するクラスを生成します。 そのデリゲートの型は Comparison
です。 Comparison
デリゲート型はジェネリック型です。 ジェネリックの詳細については、こちらを参照してください。
構文は変数を宣言しているように見えるかもしれませんが、実際には "型" を宣言していることに注意してください。 デリゲート型は、クラス内で定義したり、名前空間内で直接定義したりできるだけでなく、さらにグローバル名前空間でも定義できます。
注意
グローバル名前空間で直接デリゲート型 (またはその他の型) を宣言することは、お勧めしません。
コンパイラは、この新しい型用の追加ハンドラーと削除ハンドラーも生成するため、このクラスのクライアントは、インスタンスの呼び出しリストでメソッドを追加したり削除したりできます。 コンパイラでは、追加または削除されるメソッドのシグネチャが、メソッドの宣言時に使用されたシグネチャと一致することが強制されます。
デリゲートのインスタンスを宣言する
デリゲートを定義した後に、その型のインスタンスを作成できます。 C# のすべての変数と同様に、デリゲート インスタンスを名前空間で直接宣言することも、グローバル名前空間で宣言することもできません。
// inside a class definition:
// Declare an instance of that type:
public Comparison<T> comparator;
変数の型は、前に定義したデリゲート型の Comparison<T>
です。 変数の名前は、comparator
です。
上記のコード スニペットでは、クラス内でメンバー変数を宣言しました。 ローカル変数であるデリゲート変数、またはメソッドの引数も宣言できます。
デリゲートを呼び出す
デリゲートの呼び出しリストに含まれているメソッドを呼び出すには、そのデリゲートを呼び出します。 Sort()
メソッドの内部で、コードは比較メソッドを呼び出して、オブジェクトを配置する順番を決定します。
int result = comparator(left, right);
上の行のコードは、デリゲートにアタッチされているメソッドを "呼び出し" ます。 変数をメソッド名として扱い、通常のメソッド呼び出しの構文を使用して呼び出します。
このコード行は、安全でない想定を行っています。つまり、ターゲットがデリゲートに追加済みであるという保証がありません。 ターゲットがアタッチされていない場合、上の行によって NullReferenceException
がスローされます。 この問題に対処するための用法は、単純な null チェックよりも複雑で、このシリーズの後の方で説明します。
呼び出しターゲットの割り当て、追加、および削除を行う
デリゲート型の定義方法と、デリゲート インスタンスの宣言および呼び出しの方法は以上です。
List.Sort()
メソッドを使用する開発者は、シグネチャがデリゲート型の定義と一致するメソッドを定義し、sort メソッドによって使用されるデリゲートに割り当てる必要があります。 この割り当てによって、そのデリゲート オブジェクトの呼び出しリストにメソッドが追加されます。
文字列のリストを、長さを基準にして並べ替えるとします。 比較関数は、次のようになります。
private static int CompareLength(string left, string right) =>
left.Length.CompareTo(right.Length);
メソッドは、プライベート メソッドとして宣言されます。 それでかまいません。 このメソッドをパブリック インターフェイスに含めることはできないかもしれません。 それでも、デリゲートにアタッチすれば、比較メソッドとして使用できます。 呼び出しコードでは、このメソッドがデリゲート オブジェクトのターゲット リストにアタッチされるため、そのデリゲートを通じてこのメソッドにアクセスできます。
この関係を作成するには、このメソッドを List.Sort()
メソッドに渡します。
phrases.Sort(CompareLength);
メソッド名が、かっこなしで使用されることに注意してください。 メソッドを引数として使用すると、コンパイラは、メソッドの参照を、デリゲート呼び出しターゲットとして使用できる参照に変換し、そのメソッドを呼び出しターゲットとしてアタッチします。
Comparison<string>
型の変数を宣言し、割り当てを行うことで、明示的にそうすることもできました。
Comparison<string> comparer = CompareLength;
phrases.Sort(comparer);
デリゲート ターゲットとして使用されているメソッドが小さなメソッドである場合は、ラムダ式構文を使用して割り当てを実行することが一般的です。
Comparison<string> comparer = (left, right) => left.Length.CompareTo(right.Length);
phrases.Sort(comparer);
デリゲート ターゲットにラムダ式を使用する方法については、後のセクションで詳しく説明しています。
Sort() の例では、通常、デリゲートに 1 つのターゲット メソッドをアタッチします。 ただし、デリゲート オブジェクトは、複数のターゲット メソッドがデリゲート オブジェクトにアタッチされている呼び出しリストをサポートしています。
Delegate クラスと MulticastDelegate クラス
前述の言語サポートは、デリゲートの操作で一般的に必要となる機能と支援を提供します。 これらの機能は、.NET Core Framework の 2 つのクラスである Delegate と MulticastDelegate に基づいています。
System.Delegate
クラスとその単一の直接的なサブクラス System.MulticastDelegate
では、デリゲートの作成、デリゲート ターゲットとしてのメソッドの登録、デリゲート ターゲットとして登録されているすべてのメソッドの呼び出しに対するフレームワークのサポートが提供されます。
興味深いことに、System.Delegate
クラスと System.MulticastDelegate
クラス自体は、デリゲート型ではありません。 これらのクラスは、すべてのデリゲート型の基礎となります。 この言語設計プロセスにより、Delegate
または MulticastDelegate
から派生するクラスを宣言することはできません。 C# 言語の規則によって禁止されています。
代わりに、C# 言語キーワードを使用してデリゲート型を宣言すると、C# コンパイラは、MulticastDelegate
から派生したクラスのインスタンスを作成します。
この設計は、C# と .NET の最初のリリースが起源になっています。 設計チームの 1 つの目標は、デリゲートを使用するときに言語によってタイプ セーフが強制的に適用されるようにすることでした。 つまり、デリゲートが確実に適切な型と数の引数で呼び出されるようにすることでした。 また、戻り値の型がコンパイル時に正しく指定されるようにする必要もありました。 デリゲートは 1.0 .NET リリースに含まれており、ジェネリックより前のことでした。
このタイプ セーフを強制的に適用する最良の方法は、使用されるメソッド シグネチャを表す具体的なデリゲート クラスをコンパイラで作成することでした。
派生クラスを直接作成することはできませんが、これらのクラスで定義されているメソッドを使用することはできます。 それでは、デリゲートを操作するときに使用する最も一般的なメソッドを見ていきましょう。
まず、覚えておくべき最も重要な事実は、操作するすべてのデリゲートが MulticastDelegate
から派生しているということです。 マルチキャスト デリゲートとは、デリゲートを通じて呼び出す場合に複数のメソッド ターゲットを呼び出すことができることを意味します。 元の設計では、アタッチして呼び出すことができるターゲット メソッドが 1 つのみのデリゲートと、複数のターゲット メソッドをアタッチして呼び出すことができるデリゲートを区別することが考慮されていました。 この区別は、実際には当初考えていたよりも役に立たないことがわかりました。 この異なる 2 つのクラスは既に作成され、初期の公開リリースからフレームワークに含まれていました。
デリゲートと共に最もよく使用するメソッドは、Invoke()
と BeginInvoke()
/ EndInvoke()
です。 Invoke()
は、特定のデリゲート インスタンスにアタッチされているすべてのメソッドを呼び出します。 前に説明したように、通常はデリゲート変数に対するメソッド呼び出し構文を使用して、デリゲートを呼び出します。 このシリーズの後半で説明するように、これらのメソッドを直接操作するパターンは複数あります。
デリゲートをサポートしている言語構文とクラスの説明は以上です。次に、厳密に型指定されたデリゲートの使用、作成、呼び出しの方法を確認しましょう。
.NET