20 人の代理人
20.1 全般
デリゲート宣言は、クラス System.Delegate
から派生したクラスを定義します。 デリゲート インスタンスは、1 つ以上のメソッドのリストである 呼び出しリストをカプセル化し、それぞれが 呼び出し可能なエンティティと呼ばれます。 インスタンス メソッドの場合、呼び出し可能なエンティティは、インスタンスとそのインスタンスのメソッドで構成されます。 静的メソッドの場合、呼び出し可能なエンティティはメソッドだけで構成されます。 適切な引数のセットを使用してデリゲート インスタンスを呼び出すと、デリゲートの呼び出し可能な各エンティティが、指定された一連の引数で呼び出されます。
注:デリゲート インスタンスの興味深く有用なプロパティは、それがカプセル化するメソッドのクラスを知らないか、または気にしないことです。重要なのは、それらのメソッドがデリゲートの型と互換性があること (§20.4) ということです。 これにより、デリゲートは "匿名" 呼び出しに完全に適しています。 end note
20.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 は、 §23.2 で定義されています。
デリゲート宣言で同じ修飾子が複数回出現するのはコンパイル時エラーです。
variant_type_parameter_listを提供するデリゲート宣言は、汎用デリゲート宣言です。 さらに、ジェネリック クラス宣言またはジェネリック構造体宣言内に入れ子になったデリゲートは、それ自体がジェネリック デリゲート宣言です。これは、包含型の型引数を指定して構築型 (§8.4) を作成する必要があるためです。
new
修飾子は、別の型内で宣言されたデリゲートでのみ許可されます。その場合、このようなデリゲートは、§15.3.5 で説明されているように、同じ名前で継承されたメンバーを非表示にすることを指定。
public
、protected
、internal
、およびprivate
修飾子は、デリゲート型のアクセシビリティを制御します。 デリゲート宣言が発生するコンテキストによっては、これらの修飾子の一部が許可されない場合があります (§7.5.2)。
デリゲートの型名は identifier です。
メソッド (§15.6.1) と同様に、 ref
が存在する場合、デリゲートは by-ref を返します。それ以外の場合は、 return_type が void
の場合、デリゲートは値を返しません。それ以外の場合は、デリゲートは値を返します。
省略可能な parameter_list は、デリゲートのパラメーターを指定します。
returns-by-value デリゲート宣言または returns-no-value デリゲート宣言の return_type は、デリゲートによって返される結果の型 (存在する場合) を指定します。
returns-by-ref デリゲート宣言の ref_return_type は、デリゲートによって返される variable_reference (§9.5) によって参照される変数の型を指定します。
省略可能な variant_type_parameter_list (§18.2.3) は、デリゲート自体に対する型パラメーターを指定します。
デリゲート型の戻り値の型は、 void
または出力セーフ (§18.2.3.2) のいずれかになります。
デリゲート型のすべてのパラメーター型は、入力セーフである必要があります (§18.2.3.2)。 さらに、出力または参照パラメーターの型も出力セーフである必要があります。
注: 出力パラメーターは、一般的な実装制限により、入力セーフである必要があります。 end note
さらに、デリゲートの任意の型パラメーターに対する各クラス型制約、インターフェイス型制約、および型パラメーター制約は、入力セーフである必要があります。
C# のデリゲート型は名前と同等であり、構造的には同等ではありません。
例:
delegate int D1(int i, double d); delegate int D2(int c, double d);
デリゲート型
D1
とD2
は 2 つの異なる型であるため、同一のシグネチャにもかかわらず交換可能ではありません。end の例
他のジェネリック型宣言と同様に、型引数を指定して、構築されたデリゲート型を作成する必要があります。 構築されたデリゲート型のパラメーター型と戻り値の型は、デリゲート宣言内の型パラメーターごとに、構築されたデリゲート型の対応する型引数を置き換えることによって作成されます。
デリゲート型を宣言する唯一の方法は、 delegate_declarationを使用する方法です。 すべてのデリゲート型は、 System.Delegate
から派生した参照型です。 すべてのデリゲート型に必要なメンバーの詳細については、 §20.3 を参照してください。 デリゲート型は暗黙的に sealed
されるため、デリゲート型から型を派生させるのは許可されません。 また、 System.Delegate
から派生する非デリゲート クラス型を宣言することも許可されません。 System.Delegate
自体がデリゲート型ではありません。これは、すべてのデリゲート型の派生元となるクラス型です。
20.3 メンバーの委任
すべてのデリゲート型は、§15.3.4 で説明されているように、Delegate
クラスからメンバーを継承します。 さらに、すべてのデリゲート型は、パラメーター リストがデリゲート宣言のparameter_listと一致し、戻り値の型がデリゲート宣言のreturn_typeまたはref_return_typeと一致する非ジェネリック Invoke
メソッドと、デリゲート宣言内のref_kindが一致する returns-by-ref デリゲートを提供します。 Invoke
メソッドは、少なくとも、包含デリゲート型と同じくらいアクセス可能である必要があります。 デリゲート型で Invoke
メソッドを呼び出すことは、デリゲート呼び出し構文 (§20.6) を使用することと意味的に同じです。
実装では、デリゲート型に追加のメンバーを定義できます。
インスタンス化を除き、クラスまたはクラス インスタンスに適用できる操作は、それぞれデリゲート クラスまたはインスタンスにも適用できます。 特に、通常のメンバー アクセス構文を使用して、 System.Delegate
型のメンバーにアクセスできます。
20.4 デリゲートの互換性
メソッドまたはデリゲート型 M
は 互換性があります 次のすべてが true の場合、デリゲート型は 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
と互換性があります。end の例
例:
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>
と互換性があります。end の例
注: デリゲートの互換性の直感的な意味は、デリゲートのすべての呼び出しを型セーフに違反することなくメソッドの呼び出しに置き換え、省略可能なパラメーターとパラメーター配列を明示的なパラメーターとして扱う場合、メソッドはデリゲート型と互換性があるということです。 たとえば、次のコードのようになります。
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>
と互換性がなくなります。end note
20.5 デリゲートのインスタンス化
デリゲートのインスタンスは、デリゲート型、デリゲートの組み合わせ、またはデリゲートの削除への変換 delegate_creation_expression (§12.8.16.6) によって作成されます。 新しく作成されたデリゲート インスタンスは、次の 1 つ以上を参照します。
- delegate_creation_expressionで参照される静的メソッド、または
- ターゲット オブジェクト (
null
できない) と、 delegate_creation_expressionで参照されるインスタンス メソッド。 - 別のデリゲート (§12.8.16.6)。
例:
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 } }
end の例
デリゲート インスタンスによってカプセル化されたメソッドのセットは、 呼び出しリストと呼ばれます。 デリゲート インスタンスが 1 つのメソッドから作成されると、そのメソッドがカプセル化され、その呼び出しリストに含まれるエントリは 1 つだけです。 ただし、null
でない 2 つのデリゲート インスタンスを結合すると、その呼び出しリストが連結され、左オペランドと右オペランドの順に連結され、2 つ以上のエントリを含む新しい呼び出しリストが形成されます。
1 つのデリゲートから新しいデリゲートが作成されると、結果の呼び出しリストには、ソース デリゲート (§12.8.16.6) であるエントリが 1 つだけ含まれます。
デリゲートは、バイナリ +
(§12.10.5) と +=
演算子 (§12.21.4) を使用して結合されます。 デリゲートは、バイナリ -
(§12.10.6) と -=
演算子 (§12.21.4) を使用して、デリゲートの組み合わせから削除できます。 デリゲートは等しいかどうかを比較できます (§12.12.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
の一覧であり、それらのメソッドはcd3
によって呼び出されるのと同じ順序でtd3
によって呼び出されます。 同様に、td4
がインスタンス化されると、その呼び出しリストには 2 つのエントリしかありませんが、cd4
と同じように、M1
、M2
、M1
の 3 つのメソッドが呼び出されます。呼び出しリストの構造は、デリゲートの減算に影響します。 デリゲート
cd6
。M1
、M2
、M1
を呼び出す)cd4
からcd2
(M2
を呼び出す) を減算することによって作成されたM1
とM1
を呼び出します。 ただし、デリゲートtd6
は、td4
(M1
、M2
、M1
を呼び出す) からcd2
(M2
を呼び出す) を減算することによって作成され、M1
、M2
、およびM1
を引き続き呼び出します。これは、M2
がリスト内の単一のエントリではなく、入れ子になったリストのメンバーであるためです。 デリゲートの結合 (および削除) のその他の例については、 §20.6 を参照してください。end の例
インスタンス化されると、デリゲート インスタンスは常に同じ呼び出しリストを参照します。
注: 2 つのデリゲートを結合するか、1 つを別のデリゲートから削除すると、新しいデリゲートが独自の呼び出しリストで結果されます。結合または削除されたデリゲートの呼び出しリストは変更されません。 end note
20.6 デリゲート呼び出し
C# には、デリゲートを呼び出すための特別な構文が用意されています。 呼び出しリストに 1 つのエントリが含まれるnull
でないデリゲート インスタンスが呼び出されると、指定されたのと同じ引数を持つ 1 つのメソッドが呼び出され、参照されるメソッドと同じ値が返されます。 (デリゲート呼び出しの詳細については、 §12.8.9.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
end の例
ECMA C# draft specification