21.1 全般
デリゲート宣言は、クラス System.Delegateから派生したクラスを定義します。 デリゲート インスタンスは、1 つ以上のメソッドのリストである 呼び出しリストをカプセル化し、それぞれが 呼び出し可能なエンティティと呼ばれます。 インスタンス メソッドの場合、呼び出し可能なエンティティは、インスタンスとそのインスタンスのメソッドで構成されます。 静的メソッドの場合、呼び出し可能なエンティティはメソッドだけで構成されます。 適切な引数のセットを使用してデリゲート インスタンスを呼び出すと、デリゲートの呼び出し可能な各エンティティが、指定された一連の引数で呼び出されます。
注: デリゲート インスタンスの興味深く便利なプロパティは、それがカプセル化するメソッドのクラスを認識したり、気にしたりしないものです。重要なのは、これらのメソッドがデリゲートの型と互換性がある (§21.4) ということです。 これにより、デリゲートは "匿名" 呼び出しに完全に適しています。 注釈
21.2 デリゲート宣言
delegate_declaration は、新しいデリゲート型を宣言する type_declaration (§14.7) です。
delegate_declaration
: attributes? delegate_modifier* 'delegate' return_type delegate_header
| attributes? delegate_modifier* 'delegate' ref_kind ref_return_type
delegate_header
;
delegate_header
: identifier '(' parameter_list? ')' ';'
| identifier variant_type_parameter_list '(' parameter_list? ')'
type_parameter_constraints_clause* ';'
;
delegate_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| unsafe_modifier // unsafe code support
;
unsafe_modifier は §24.2 で定義されています。
デリゲート宣言で同じ修飾子が複数回出現するのは、コンパイル時のエラーです。
variant_type_parameter_list を提供するデリゲート宣言は、汎用デリゲート宣言です。 さらに、ジェネリック クラス宣言またはジェネリック構造体宣言内に入れ子になったデリゲートは、それ自体がデリゲート クラス宣言です。これは、包含型の型引数を指定して構築型 (§8.4) を作成する必要があるためです。
new 修飾子は、別の型内で宣言されたデリゲートでのみ許可されます。その場合、このようなデリゲートは、 §15.3.5 で説明されているように、同じ名前で継承されたメンバーを非表示にすることを指定します。
public、 protected、 internal、および private 修飾子は、デリゲート型のアクセシビリティを制御します。 デリゲート宣言が発生するコンテキストによっては、これらの修飾子の一部が許可されない場合があります (§7.5.2)。
デリゲートの型名は identifier です。
メソッド (§15.6.1) と同様、 ref が存在する場合、デリゲートは returns-by-ref となり、それ以外では、 return_type が voidの場合、デリゲートは returns-no-value となり、それ以外の場合、デリゲートは returns-by-value となります。
省略可能な parameter_list は、デリゲートのパラメーターを指定します。
returns-by-value デリゲート宣言または returns-no-value デリゲート宣言の return_type は、デリゲートによって返される結果の型 (存在する場合) を指定します。
returns-by-ref デリゲート宣言の ref_return_type は、デリゲートによって返される variable_reference (§9.5) によって参照される変数の型を指定します。
省略可能 なvariant_type_parameter_list (§19.2.3) は、デリゲート自体に対する型パラメーターを指定します。
デリゲート型の戻り値の型は、 voidまたは出力セーフ (§19.2.3.2) である必要があります。
デリゲート型のすべてのパラメーター型は、入力セーフである必要があります (§19.2.3.2)。 さらに、出力または参照パラメーターの型も output-safe である必要があります。
注: 出力パラメーターは、一般的な実装制限により、input-safe である必要があります。 注釈
さらに、デリゲートの任意の型パラメーターに対する各クラス型制約、インターフェイス型制約、型パラメーター制約は、input-safe である必要があります。
C# のデリゲート型は名前と同等であり、構造的には同等ではありません。
例:
delegate int D1(int i, double d); delegate int D2(int c, double d);デリゲート型
D1とD2は 2 つの異なる型であるため、同一のシグネチャであっても、交換可能ではありません。終了サンプル
他のジェネリック型宣言と同様、型引数を指定して、構築されたデリゲート型を作成する必要があります。 構築されたデリゲート型のパラメーター型と戻り値の型は、デリゲート宣言内の型パラメーターごとに、構築されたデリゲート型の対応する型引数を置き換えることによって作成されます。
デリゲート型を宣言する唯一の方法は、 delegate_declarationを使用することです。 すべてのデリゲート型は、 System.Delegateから派生した参照型です。 すべてのデリゲート型に必要なメンバーの詳細については、 §21.3 を参照してください。 デリゲート型は暗黙的に sealedされるため、デリゲート型から型を派生させることは許可されません。 また、 System.Delegateから派生する非デリゲート クラス型を宣言することも許可されません。
System.Delegate 自体がデリゲート型ではなく、すべてのデリゲート型の派生元となるクラス型です。
21.3 メンバーの委任
すべてのデリゲート型は Delegate クラスからメンバーを継承します。詳細は §15.3.4に説明されています。 さらに、すべてのデリゲート型は、パラメーター リストがデリゲート宣言のInvoke と一致し、戻り値の型がデリゲート宣言の return_type または ref_return_type と一致する非ジェネリック メソッドと、デリゲート宣言内の ref_kind と一致する returns-by-ref デリゲートを提供します。
Invoke メソッドは、少なくとも、包含デリゲート型と同程度にアクセス可能である必要があります。 デリゲート型で Invoke メソッドを呼び出すことは、デリゲート呼び出し構文 (§21.6) を使用することと意味的に同じです。
実装では、デリゲート型に追加のメンバーを定義できます。
インスタンス化を除き、クラスまたはクラス インスタンスに適用できる操作は、それぞれデリゲート クラスまたはインスタンスにも適用できます。 特に、通常のメンバー アクセス構文を使用して、 System.Delegate 型のメンバーにアクセスできます。
21.4 デリゲートの互換性
メソッドまたはデリゲート型 M は、 次のすべてが当てはまる場合、 デリゲート型と D 互換性があります。
-
DおよびMは同じ数のパラメーターを持ち、Dの各パラメーターには、Mの対応するパラメーターと同じ参照渡しパラメーター修飾子があります。 - 各値パラメーターに対して、ID 変換 (§10.2.2) または暗黙的な参照変換 (§10.2.8) は、
Dのパラメーター型からMの対応するパラメーター型に存在します。 - 参照渡しパラメーターごとに、
Dのパラメーター型は、Mのパラメーター型と同じです。 - 次のいずれかが当てはまります。
この互換性の定義により、戻り値の型の共変性と、パラメーター型の反変性が可能になります。
例:
delegate int D1(int i, double d); delegate int D2(int c, double d); delegate object D3(string s); class A { public static int M1(int a, double b) {...} } class B { public static int M1(int f, double g) {...} public static void M2(int k, double l) {...} public static int M3(int g) {...} public static void M4(int g) {...} public static object M5(string s) {...} public static int[] M6(object o) {...} }メソッド
A.M1とB.M1は、デリゲート型D1とD2の両方と互換性があります。これは、戻り値の型とパラメーター リストが同じであるためです。 メソッドB.M2、B.M3、およびB.M4は、戻り値の型またはパラメーター リストが異なるため、D1およびD2デリゲート型と互換性がありません。 メソッドB.M5とB.M6はどちらもデリゲート型のD3と互換性があります。終了サンプル
例:
delegate bool Predicate<T>(T value); class X { static bool F(int i) {...} static bool G(string s) {...} }メソッド
X.Fはデリゲート型Predicate<int>と互換性があり、メソッドX.Gはデリゲート型のPredicate<string>と互換性があります。終了サンプル
注: デリゲートの互換性の直感的な意味は、デリゲートのすべての呼び出しを、型安全性に違反することなくメソッドの呼び出しに置き換え、省略可能なパラメーターとパラメーター配列を明示的なパラメーターとして扱う場合、メソッドはデリゲート型と互換性があるということです。 たとえば、次のコードのようになります。
delegate void Action<T>(T arg); class Test { static void Print(object value) => Console.WriteLine(value); static void Main() { Action<string> log = Print; log("text"); } }
Action<string>デリゲート型と互換性があります。Action<string>デリゲートのすべての呼び出しもたとえば、上記の
Print(object value, bool prependTimestamp = false)に変更された場合、Action<string>と互換性がなくなります。注釈
21.5 デリゲートのインスタンス化
デリゲートのインスタンスは、デリゲート型、デリゲートの組み合わせ、またはデリゲートの削除への変換である delegate_creation_expression (§12.8.17.5) によって作成されます。 新しく作成されたデリゲート インスタンスは、次の 1 つ以上を参照します。
- delegate_creation_expressionで参照される静的メソッド、または
- ターゲット オブジェクト (
nullにはできない) と、 delegate_creation_expressionで参照されるインスタンス メソッド、または - 別のデリゲート (§12.8.17.5)。
例:
delegate void D(int x); class C { public static void M1(int i) {...} public void M2(int i) {...} } class Test { static void Main() { D cd1 = new D(C.M1); // Static method C t = new C(); D cd2 = new D(t.M2); // Instance method D cd3 = new D(cd2); // Another delegate } }終了サンプル
デリゲート インスタンスによってカプセル化されたメソッドのセットは、 呼び出しリストと呼ばれます。 デリゲート インスタンスが 1 つのメソッドから作成されると、そのメソッドがカプセル化され、その呼び出しリストに含まれるエントリは 1 つだけです。 ただし、null でない 2 つのデリゲート インスタンスを結合すると、その呼び出しリストが (左オペランド、右オペランドの順に) 連結され、2 つ以上のエントリを含む新しい呼び出しリストが形成されます。
1 つのデリゲートから新しいデリゲートが作成されると、結果の呼び出しリストには、ソース デリゲート (§12.8.17.5) であるエントリが 1 つだけ含まれます。
デリゲートは、バイナリ + (§12.12.5) と += 演算子 (§12.23.4) を使用して結合されます。 デリゲートは、バイナリ - (§12.12.6) と -= 演算子 (§12.23.4) を使用して、デリゲートの組み合わせから削除できます。 デリゲートは等しいかどうかを比較できます (§12.14.9)。
例: 次の例は、多数のデリゲートのインスタンス化とそれに対応する呼び出しリストを示しています。
delegate void D(int x); class C { public static void M1(int i) {...} public static void M2(int i) {...} } class Test { static void Main() { D cd1 = new D(C.M1); // M1 - one entry in invocation list D cd2 = new D(C.M2); // M2 - one entry D cd3 = cd1 + cd2; // M1 + M2 - two entries D cd4 = cd3 + cd1; // M1 + M2 + M1 - three entries D cd5 = cd4 + cd3; // M1 + M2 + M1 + M1 + M2 - five entries D td3 = new D(cd3); // [M1 + M2] - ONE entry in invocation // list, which is itself a list of two methods. D td4 = td3 + cd1; // [M1 + M2] + M1 - two entries D cd6 = cd4 - cd2; // M1 + M1 - two entries in invocation list D td6 = td4 - cd2; // [M1 + M2] + M1 - two entries in invocation list, // but still three methods called, M2 not removed. } }
cd1とcd2がインスタンス化されると、それぞれが 1 つのメソッドをカプセル化します。cd3がインスタンス化されると、M1とM2の 2 つのメソッドの呼び出しリストがその順序で表示されます。cd4の呼び出しリストには、その順序でM1、M2、およびM1が含まれています。cd5の場合、呼び出しリストには、その順序でM1、M2、M1、M1、およびM2が含まれます。delegate_creation_expression を持つ別のデリゲートからデリゲートを作成すると、結果には元の構造とは異なる呼び出しリストが含まれますが、同じメソッドが同じ順序で呼び出されます。
td3がcd3から作成されると、その呼び出しリストにはメンバーが 1 つしかありませんが、そのメンバーはM1メソッドとM2メソッドの一覧であり、それらのメソッドはtd3によって呼び出されるのと同じ順序でcd3によって呼び出されます。 同様に、td4がインスタンス化されると、その呼び出しリストには 2 つのエントリしか含まれませんが、M1と同じようにその順序で、M2、M1、cd4の 3 つのメソッドが呼び出されます。呼び出しリストの構造は、デリゲートの減算に影響します。 デリゲート
cd6cd2(M2を呼び出す) は、cd4から (M1、M2、M1を呼び出す) を減算することによって作成され、M1とM1を呼び出します。 ただし、デリゲートtd6は、cd2(M2、td4、M1を呼び出す) からM2(M1を呼び出す) を減算することによって作成され、M1、M2およびM1をその順序で引き続き呼び出します。これは、M2がリスト内の単一のエントリではなく、入れ子になったリストのメンバーであるためです。 デリゲートの結合 (および削除) のその他の例については、 §21.6 を参照してください。終了サンプル
インスタンス化されると、デリゲート インスタンスは常に同じ呼び出しリストを参照します。
注: 2 つのデリゲートを結合するか、1 つを別のデリゲートから削除すると、独自の呼び出しリストを含む新しいデリゲートが生成されます。結合または削除されたデリゲートの呼び出しリストは変更されません。 注釈
21.6 デリゲート呼び出し
C# には、デリゲートを呼び出すための特別な構文が用意されています。 呼び出しリストに 1 つのエントリが含まれるnull でないデリゲート インスタンスが呼び出されると、指定されたのと同じ引数を持つ 1 つのメソッドが呼び出され、参照されるメソッドと同じ値が返されます。 (デリゲート呼び出しの詳細については、§12.8.10.4 を参照してください)。このようなデリゲートの呼び出し中に例外が発生し、その例外が呼び出されたメソッド内でキャッチされない場合は、デリゲートを呼び出したメソッドで例外 catch 句の検索が続行されます。これは、そのメソッドがそのデリゲートを参照するメソッドを直接呼び出した場合と同様です。
呼び出しリストに複数のエントリが含まれるデリゲート インスタンスの呼び出しは、呼び出しリスト内の各メソッドを順番に同期的に呼び出すことによって続行されます。 呼び出された各メソッドには、デリゲート インスタンスに渡されたのと同じ一連の引数が渡されます。 このようなデリゲート呼び出しに参照パラメーター (§15.6.2.3.3) が含まれている場合、各メソッド呼び出しは同じ変数への参照で発生します。呼び出しリスト内の 1 つのメソッドによるその変数への変更は、呼び出しリストのさらに下にあるメソッドに表示されます。 デリゲートの呼び出しに出力パラメーターまたは戻り値が含まれている場合、最終的な値はリスト内の最後のデリゲートの呼び出しから取得されます。 このようなデリゲートの呼び出しの処理中に例外が発生し、その例外が呼び出されたメソッド内でキャッチされない場合は、デリゲートを呼び出したメソッドで例外 catch 句の検索が続行され、呼び出しリストのさらに下にあるメソッドは呼び出されません。
値が null のデリゲート インスタンスを呼び出そうとすると、 System.NullReferenceException型の例外が発生します。
例: 次の例は、デリゲートをインスタンス化、結合、削除、呼び出す方法を示しています。
delegate void D(int x); class C { public static void M1(int i) => Console.WriteLine("C.M1: " + i); public static void M2(int i) => Console.WriteLine("C.M2: " + i); public void M3(int i) => Console.WriteLine("C.M3: " + i); } class Test { static void Main() { D cd1 = new D(C.M1); cd1(-1); // call M1 D cd2 = new D(C.M2); cd2(-2); // call M2 D cd3 = cd1 + cd2; cd3(10); // call M1 then M2 cd3 += cd1; cd3(20); // call M1, M2, then M1 C c = new C(); D cd4 = new D(c.M3); cd3 += cd4; cd3(30); // call M1, M2, M1, then M3 cd3 -= cd1; // remove last M1 cd3(40); // call M1, M2, then M3 cd3 -= cd4; cd3(50); // call M1 then M2 cd3 -= cd2; cd3(60); // call M1 cd3 -= cd2; // impossible removal is benign cd3(60); // call M1 cd3 -= cd1; // invocation list is empty so cd3 is null // cd3(70); // System.NullReferenceException thrown cd3 -= cd1; // impossible removal is benign } }ステートメント
cd3 += cd1;に示すように、デリゲートは呼び出しリストに複数回存在できます。 この場合、1 回出現ごとに 1 回だけ呼び出されます。 このような呼び出しリストでは、そのデリゲートが削除されると、呼び出しリスト内で最後に出現するのは実際に削除されたものになります。最後のステートメント
cd3 -= cd1; の実行直前に、デリゲートcd3は空の呼び出しリストを参照します。 空のリストからデリゲートを削除する (または空でないリストから存在しないデリゲートを削除する) 試みはエラーではありません。生成される出力は次のとおりです。
C.M1: -1 C.M2: -2 C.M1: 10 C.M2: 10 C.M1: 20 C.M2: 20 C.M1: 20 C.M1: 30 C.M2: 30 C.M1: 30 C.M3: 30 C.M1: 40 C.M2: 40 C.M3: 40 C.M1: 50 C.M2: 50 C.M1: 60 C.M1: 60終了サンプル
ECMA C# draft specification