20 Stellvertretungen
20.1 Allgemein
Eine Delegatdeklaration definiert eine Klasse, die von der Klasse System.Delegate
abgeleitet wird. Eine Stellvertretungsinstanz kapselt eine Aufrufliste, bei der es sich um eine Liste mit einer oder mehreren Methoden handelt, die jeweils als aufrufbare Entität bezeichnet wird. Beispielsweise besteht eine aufrufbare Entität aus einer Instanz und einer Methode für diese Instanz. Bei statischen Methoden besteht eine aufrufbare Entität nur aus einer Methode. Das Aufrufen einer Delegateninstanz mit einer entsprechenden Gruppe von Argumenten bewirkt, dass jede aufrufbare Entität der Stellvertretung mit der angegebenen Gruppe von Argumenten aufgerufen wird.
Hinweis: Eine interessante und nützliche Eigenschaft einer Stellvertretungsinstanz besteht darin, dass sie die Klassen der methoden, die sie kapselt, nicht kennen oder interessieren. Das alles ist wichtig, dass diese Methoden mit dem Typ des Delegaten kompatibel sind (§20.4). Dadurch eignen sich Stellvertretungen perfekt für "anonyme" Aufrufe. Endnote
20.2 Stellvertretungsdeklarationen
Ein delegate_declaration ist ein type_declaration (§14.7), der einen neuen Delegattyp deklariert.
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 ist in §23.2 definiert.
Es handelt sich um einen Kompilierungsfehler für denselben Modifizierer, der mehrmals in einer Delegatdeklaration angezeigt wird.
Eine Delegatdeklaration, die eine variant_type_parameter_list bereitstellt, ist eine generische Delegatdeklaration. Darüber hinaus ist jede Stellvertretung, die in einer generischen Klassendeklaration oder einer generischen Strukturdeklaration geschachtelt ist, selbst eine generische Delegatdeklaration, da Typargumente für den enthaltenden Typ bereitgestellt werden müssen, um einen konstruierten Typ (§8.4) zu erstellen.
Der new
Modifizierer ist nur für Stellvertretungen zulässig, die innerhalb eines anderen Typs deklariert sind. In diesem Fall gibt er an, dass ein solcher Delegat ein geerbtes Mitglied unter demselben Namen ausblendet, wie in §15.3.5 beschrieben.
Die public
Modifizierer protected
, , internal
und private
Die Modifizierer steuern die Barrierefreiheit des Delegattyps. Abhängig vom Kontext, in dem die Stellvertretungsdeklaration auftritt, sind einige dieser Modifizierer möglicherweise nicht zulässig (§7.5.2).
Der Typname der Stellvertretung ist bezeichner.
Wie bei Methoden (§15.6.1) gibt ref
die Stellvertretung den Rückgabe-by-Ref zurück, andernfalls, wenn return_type ist void
, gibt der Delegat den Wert "no-value" zurück. Andernfalls gibt der Delegat den Wert zurück.
Die optionale parameter_list gibt die Parameter des Delegaten an.
Die return_type einer Rückgabe-nach-Wert- oder Rückgabe-No-Value-Delegatdeklaration gibt den Typ des Ergebnisses (sofern vorhanden) an, der vom Delegaten zurückgegeben wird.
Die ref_return_type einer Rückgabe-nach-Bezug-Deklaration gibt den Typ der Variablen an, auf die von der Stellvertretung zurückgegebene variable_reference (§9.5) verwiesen wird.
Die optionale variant_type_parameter_list (§18.2.3) gibt die Typparameter für den Delegaten selbst an.
Der Rückgabetyp eines Stellvertretungstyps muss entweder void
oder ausgabesicher (§18.2.3.2) sein.
Alle Parametertypen eines Stellvertretungstyps müssen eingabesicher sein (§18.2.3.2). Darüber hinaus müssen alle Ausgabe- oder Referenzparametertypen auch ausgabesicher sein.
Hinweis: Ausgabeparameter müssen aufgrund allgemeiner Implementierungseinschränkungen eingabesicher sein. Endnote
Darüber hinaus müssen jede Klassentypeinschränkung, Schnittstellentypeinschränkung und Typparametereinschränkung für alle Typparameter des Delegaten eingabesicher sein.
Delegattypen in C# sind namensäquivalent, nicht strukturell gleichwertig.
Beispiel:
delegate int D1(int i, double d); delegate int D2(int c, double d);
Die Delegattypen
D1
undD2
zwei verschiedene Typen, daher sind sie trotz ihrer identischen Signaturen nicht austauschbar.Endbeispiel
Wie andere generische Typdeklarationen müssen Typargumente angegeben werden, um einen konstruierten Delegattyp zu erstellen. Die Parametertypen und der Rückgabetyp eines konstruierten Delegattyps werden durch Substituieren für jeden Typparameter in der Delegatdeklaration das entsprechende Typargument des konstruierten Delegatentyps erstellt.
Die einzige Möglichkeit, einen Delegattyp zu deklarieren, ist über eine delegate_declaration. Jeder Delegattyp ist ein Verweistyp, der von System.Delegate
. Die für jeden Stellvertretungstyp erforderlichen Mitglieder sind in §20.3 beschrieben. Delegattypen sind implizit sealed
, sodass es nicht zulässig ist, einen Typ von einem Delegattyp abzuleiten. Es ist auch nicht zulässig, einen nicht delegierten Klassentyp zu deklarieren, der von System.Delegate
. System.Delegate
ist nicht selbst ein Delegattyp; Es handelt sich um einen Klassentyp, von dem alle Delegattypen abgeleitet werden.
20.3 Delegierte Mitglieder
Jeder Delegattyp erbt Member von der Delegate
Klasse, wie in §15.3.4 beschrieben. Darüber hinaus stellt jeder Delegattyp eine nicht generische Invoke
Methode bereit, deren Parameterliste mit der parameter_list in der Stellvertretungsdeklaration übereinstimmt, deren Rückgabetyp der return_type oder ref_return_type in der Delegatdeklaration entspricht, und für Rückgabe-nach-Verweis-Delegaten, deren ref_kind mit der Stellvertretungsdeklaration übereinstimmt. Die Invoke
Methode muss mindestens so zugänglich sein wie der enthaltende Delegattyp. Das Aufrufen der Invoke
Methode für einen Delegatentyp entspricht semantisch der Verwendung der Stellvertretungssyntax (§20.6).
Implementierungen können zusätzliche Member im Delegattyp definieren.
Mit Ausnahme der Instanziierung können alle Vorgänge, die auf eine Klasse oder Klasseninstanz angewendet werden können, auch auf eine Delegatenklasse oder Instanz angewendet werden. Insbesondere ist es möglich, über die übliche Memberzugriffssyntax auf Member des System.Delegate
Typs zuzugreifen.
20.4 Stellvertretungskompatibilität
Eine Methode oder ein Delegattyp M
ist mit einem Delegattyp D
kompatibel, wenn alle folgenden Werte zutreffen:
D
undM
die gleiche Anzahl von Parametern aufweisen, und jeder Parameter inD
verfügt über denselben Zusatzparameter wie der entsprechende Parameter inM
.- Für jeden Wertparameter ist eine Identitätskonvertierung (§10.2.2) oder implizite Verweiskonvertierung (§10.2.8) aus dem Parametertyp in
D
den entsprechenden Parametertyp vorhanden inM
. - Für jeden By-Reference-Parameter ist der Parametertyp
D
identisch mit dem Parametertyp inM
. - Eine der folgenden Punkte ist wahr:
D
undM
sind beide Rückgaben ohne Wert.D
undM
sind Rückgabe-nach-Wert (§15.6.1, §20.2), und eine Identitäts- oder implizite Verweiskonvertierung ist vom Rückgabetyp bisM
zum Rückgabetyp vonD
.D
undM
sind beide Rückgabe-by-Ref, eine Identitätskonvertierung ist zwischen dem RückgabetypM
und dem Rückgabetyp vonD
vorhanden, und beide haben dieselbe ref_kind.
Diese Definition der Kompatibilität ermöglicht die Kovarianz in Rückgabetyp und Kontravarianz in Parametertypen.
Beispiel:
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) {...} }
Die Methoden
A.M1
sindB.M1
sowohl mit den DelegattypenD1
D2
als auch kompatibel, da sie über denselben Rückgabetyp und dieselbe Parameterliste verfügen. Die MethodenB.M2
,B.M3
, undB.M4
sind mit den DelegattypenD1
nicht kompatibel,D2
da sie unterschiedliche Rückgabetypen oder Parameterlisten haben. Die MethodenB.M5
undB.M6
sind beide mit dem DelegattypD3
kompatibel.Endbeispiel
Beispiel:
delegate bool Predicate<T>(T value); class X { static bool F(int i) {...} static bool G(string s) {...} }
Die Methode
X.F
ist mit dem DelegattypPredicate<int>
kompatibel, und die MethodeX.G
ist mit dem DelegattypPredicate<string>
kompatibel.Endbeispiel
Hinweis: Die intuitive Bedeutung der Delegatkompatibilität besteht darin, dass eine Methode mit einem Delegattyp kompatibel ist, wenn jeder Aufruf des Delegaten durch einen Aufruf der Methode ersetzt werden kann, ohne die Typsicherheit zu verletzen, optionale Parameter und Parameterarrays als explizite Parameter zu behandeln. Beispiel: Im folgenden Code:
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"); } }
Die
Action<string>
Delegattyp kompatibel, da jeder Aufruf einesAction<string>
Delegaten auch ein gültiger Aufruf derWenn die Signatur der
Print(object value, bool prependTimestamp = false)
wurde, wäre dieAction<string>
den Regeln dieser Klausel kompatibel.Endnote
20.5 Instanziierung der Stellvertretung
Eine Instanz einer Stellvertretung wird durch eine delegate_creation_expression (§12.8.16.6), eine Konvertierung in einen Delegattyp, eine Stellvertretungskombination oder das Entfernen von Stellvertretungen erstellt. Die neu erstellte Delegateninstanz bezieht sich dann auf eine oder mehrere von:
- Die statische Methode, auf die im delegate_creation_expression verwiesen wird, oder
- Das Zielobjekt (das nicht sein
null
kann) und die Instanzmethode, auf die im delegate_creation_expression verwiesen wird, oder - Eine weitere Stellvertretung (§12.8.16.6).
Beispiel:
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 } }
Endbeispiel
Der Satz von Methoden, die von einer Stellvertretungsinstanz gekapselt werden, wird als Aufrufliste bezeichnet. Wenn eine Stellvertretungsinstanz aus einer einzigen Methode erstellt wird, kapselt sie diese Methode, und ihre Aufrufliste enthält nur einen Eintrag. Wenn jedoch zwei Nichtdelegatinstanzennull
kombiniert werden, werden ihre Aufruflisten – in der Reihenfolge des linken Operanden dann rechts – verkettet, um eine neue Aufrufliste zu bilden, die zwei oder mehr Einträge enthält.
Wenn eine neue Stellvertretung aus einer einzigen Stellvertretung erstellt wird, weist die resultierende Aufrufliste nur einen Eintrag auf, bei dem es sich um den Quelldelegat (§12.8.16.6) handelt.
Stellvertretungen werden mit den binären +
Operatoren (§12.10.5) und +=
Operatoren (§12.21.4) kombiniert. Eine Stellvertretung kann aus einer Kombination von Stellvertretungen entfernt werden, wobei die binären -
(§12.10.6) und -=
Operatoren (§12.21.4) verwendet werden. Stellvertretungen können für die Gleichheit verglichen werden (§12.12.9).
Beispiel: Das folgende Beispiel zeigt die Instanziierung einer Reihe von Stellvertretungen und deren entsprechenden Aufruflisten:
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. } }
Wenn
cd1
undcd2
instanziiert werden, kapseln sie jeweils eine Methode. Wenncd3
instanziiert wird, enthält sie eine Aufrufliste mit zwei MethodenM1
undM2
in dieser Reihenfolge.cd4
Die Aufrufliste enthältM1
,M2
und , inM1
dieser Reihenfolge. Forcd5
, the invocation list containsM1
, ,M2
,M1
,M1
, andM2
, and , in that order.Beim Erstellen einer Stellvertretung aus einer anderen Stellvertretung mit einer delegate_creation_expression hat das Ergebnis eine Aufrufliste mit einer anderen Struktur als dem Original, was jedoch dazu führt, dass dieselben Methoden in derselben Reihenfolge aufgerufen werden. Wenn
td3
sie auscd3
der Aufrufliste erstellt wird, hat nur ein Element, aber dieses Element ist eine Liste der MethodenM1
undM2
diese Methoden werden in derselben Reihenfolge aufgerufen, wietd3
sie voncd3
ihnen aufgerufen werden. Ebenso, wenntd4
ihre Aufrufliste instanziiert wird, sind nur zwei Einträge vorhanden, aber sie ruft die drei MethodenM1
,M2
undM1
in dieser Reihenfolge genauso aufcd4
.Die Struktur der Aufrufliste wirkt sich auf die Subtraktion der Stellvertretung aus. Stellvertretung
cd6
, erstellt durch Subtrahierencd2
(dieM2
aufgerufen wird ) voncd4
(die aufruftM1
,M2
und ) aufruftM1
M1
undM1
. Delegattd6
, erstellt durch Subtrahierencd2
(die aufgerufen wird ) vontd4
(dieM2
aufruftM1
M1
,M2
undM1
) weiterhin aufgerufen wird,M2
undM1
in dieser Reihenfolge, wieM2
es sich nicht um einen einzelnen Eintrag in der Liste handelt, sondern ein Mitglied einer geschachtelten Liste. Weitere Beispiele für das Kombinieren (und Entfernen) von Delegaten finden Sie unter §20.6.Endbeispiel
Nach der Instanziierung bezieht sich eine Stellvertretungsinstanz immer auf dieselbe Aufrufliste.
Hinweis: Denken Sie daran, dass, wenn zwei Stellvertretungen kombiniert werden oder eine aus einem anderen entfernt wird, eine neue Stellvertretung mit einer eigenen Aufrufliste resultiert; die Aufruflisten der kombinierten oder entfernten Stellvertretungen bleiben unverändert. Endnote
20.6 Stellvertretungsaufruf
C# stellt eine spezielle Syntax zum Aufrufen eines Delegaten bereit. Wenn eine Instanznull
ohne Stellvertretung, deren Aufrufliste einen Eintrag enthält, aufgerufen wird, ruft sie die eine Methode mit denselben Argumenten auf, die sie angegeben wurde, und gibt denselben Wert wie die bezeichnete Methode zurück. (Ausführliche Informationen zum Stellvertretungsaufruf finden Sie unter §12.8.9.4 .) Wenn während des Aufrufs eines solchen Delegaten eine Ausnahme auftritt und diese Ausnahme nicht innerhalb der aufgerufenen Methode abgefangen wird, wird die Suche nach einer Ausnahme-Catch-Klausel in der Methode fortgesetzt, die den Delegaten aufgerufen hat, als ob diese Methode direkt die Methode aufgerufen hatte, auf die dieser Delegate verwiesen hat.
Der Aufruf einer Stellvertretungsinstanz, deren Aufrufliste mehrere Einträge enthält, wird fortgesetzt, indem jede der Methoden in der Aufrufliste synchron aufgerufen wird. Jede methode, die so aufgerufen wird, wird dieselbe Gruppe von Argumenten übergeben, die an die Delegateninstanz übergeben wurden. Wenn ein solcher Stellvertretungsaufruf Referenzparameter (§15.6.2.3.3) enthält, erfolgt jeder Methodenaufruf mit einem Verweis auf dieselbe Variable. Änderungen an dieser Variablen durch eine Methode in der Aufrufliste sind für Methoden weiter unten in der Aufrufliste sichtbar. Wenn der Stellvertretungsaufruf Ausgabeparameter oder einen Rückgabewert enthält, wird der endgültige Wert aus dem Aufruf des letzten Delegaten in der Liste stammen. Wenn während der Verarbeitung des Aufrufs eines solchen Delegaten eine Ausnahme auftritt und diese Ausnahme nicht innerhalb der aufgerufenen Methode abgefangen wird, wird die Suche nach einer Ausnahme-Catch-Klausel in der Methode fortgesetzt, die den Delegaten aufgerufen hat, und alle Methoden weiter unten in der Aufrufliste werden nicht aufgerufen.
Beim Versuch, eine Stellvertretungsinstanz aufzurufen, deren Wert zu einer Ausnahme vom Typ System.NullReferenceException
führtnull
.
Beispiel: Das folgende Beispiel zeigt, wie Stellvertretungen instanziiert, kombiniert, entfernt und aufgerufen werden:
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 } }
Wie in der Anweisung
cd3 += cd1;
gezeigt, kann eine Stellvertretung mehrmals in einer Aufrufliste vorhanden sein. In diesem Fall wird sie einfach einmal pro Vorkommen aufgerufen. In einer Aufrufliste, z. B. wenn diese Stellvertretung entfernt wird, wird das letzte Vorkommen in der Aufrufliste tatsächlich entfernt.Unmittelbar vor der Ausführung der endgültigen Anweisung
cd3 -= cd1
bezieht sich der Delegatcd3
auf eine leere Aufrufliste. Der Versuch, eine Stellvertretung aus einer leeren Liste zu entfernen (oder einen nicht vorhandenen Delegat aus einer nicht leeren Liste zu entfernen), ist kein Fehler.Die erzeugte Ausgabe lautet:
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
Endbeispiel
ECMA C# draft specification