Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
21.1 Allgemein
Eine Delegatdeklaration definiert eine Klasse, die von der Klasse System.Delegate abgeleitet ist. Eine Delegatinstanz kapselt eine Aufrufliste, also eine Liste mit einzelnen oder mehreren Methoden, die jeweils als aufrufbare Entität bezeichnet werden. Bei Instanzmethoden 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 Delegatinstanz mit einem geeigneten Satz von Argumenten veranlasst das Aufrufen der aufrufbaren Entität des Delegaten mit dem gegebenen Satz von Argumenten.
Hinweis: Eine interessante und nützliche Eigenschaft einer Delegateninstanz besteht darin, dass sie die Klassen der Methoden, die sie kapselt, nicht kennen oder interessieren; all das ist wichtig, dass diese Methoden mit dem Typ der Stellvertretung kompatibel sind (§21.4). Dadurch eignen sich Delegate ideal für den „anonymen“ Aufruf. Hinweisende
21.2 Stellvertretungsdeklarationen
Eine delegate_declaration ist eine type_declaration (§14.7), die 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 §24.2 definiert.
Es handelt sich um einen Kompilierungsfehler für diesen Modifizierer, der mehrmals in einer Delegatdeklaration auftritt.
Eine Delegatdeklaration, die eine variant_type_parameter_list bereitstellt, ist eine generische Delegatdeklaration. Darüber hinaus ist jedes in einer generischen Klassendeklaration oder einer generischen Strukturdeklaration geschachtelte Delegat seinerseits eine generische Delegatdeklaration, da Typargumente für den enthaltenden Typ übergeben werden müssen, um einen konstruierten Typ (§ 8.4) zu erstellen.
Der Modifizierer new ist nur für Delegate zulässig, die in einem anderen Typ deklariert wurden. In diesem Fall gibt er an, dass ein solcher Delegat einen geerbten Member unter demselben Namen verbirgt, siehe § 15.3.5.
Die Modifizierer public, protected, internal und private kontrollieren die Zugriff auf den Delegattyp. Abhängig vom Kontext, in dem die Delegatdeklaration erfolgt, sind einige dieser Modifizierer möglicherweise nicht zulässig (§ 7.5.2).
Der Typname des Delegaten lautet identifier.
Wie bei Methoden (§ 15.6.1) nutzt das Delegat bei Vorliegen von ref returns-by-ref. Hat dagegen return_type den Wert void, nutzt der Delegat returns-no-value. Andernfalls nutzt der Delegat returns-by-value.
Die optionale parameter_list gibt die Parameter des Delegaten an.
Der return_type einer Delegatdeklaration (returns-by-value oder returns-no-value) gibt den Typ des vom Delegaten zurückgegebenen Ergebnisses an (sofern relevant).
Der ref_return_type einer returns-by-ref-Delegatdeklaration gibt den Typ der Variablen an, auf die die vom Delegaten zurückgegebene variable_reference (§ 9.5) verweist.
Die optionale variant_type_parameter_list (§19.2.3) gibt die Typparameter für den Delegaten selbst an.
Der Rückgabetyp eines Stellvertretungstyps muss entweder voidoder ausgabesicher (§19.2.3.2) sein.
Alle Parametertypen eines Stellvertretungstyps müssen eingabesicher sein (§19.2.3.2). Darüber hinaus müssen alle Ausgabe- oder Referenzparametertypen ebenfalls output-safe sein.
Hinweis: Ausgabeparameter müssen aufgrund allgemeiner Implementierungsbeschränkungen input-safe sein. Hinweisende
Darüber hinaus müssen alle Klassentypeinschränkungen, Schnittstellentypeinschränkungen und Typparametereinschränkungen für Typparameter des Delegaten input-safe sein.
Delegattypen sind in C# namensäquivalent, nicht strukturäquivalent.
Beispiel:
delegate int D1(int i, double d); delegate int D2(int c, double d);Die Delegattypen
D1undD2sind unterschiedliche Typen, also trotz identischer Signaturen nicht austauschbar.Ende des Beispiels
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 mittels Substitution jedes Typparameters in der Delegatdeklaration durch das entsprechende Typargument des konstruierten Delegattyps erstellt.
Die einzige Möglichkeit, einen Delegattyp zu deklarieren, ist delegate_declaration. Jeder Delegattyp ist ein Verweistyp, der von System.Delegate abgeleitet wird. Die für jeden Stellvertretungstyp erforderlichen Mitglieder sind in §21.3 detailliert aufgeführt. Delegattypen sind implizit sealed. Deshalb darf kein Typ von einem Delegattyp abgeleitet werden. Ebenso ist es unzulässig, einen von System.Delegate abgeleiteten Klassentyp zu deklarieren, bei dem es sich nicht um einen Delegaten handelt.
System.Delegate ist selbst kein Delegattyp. Es handelt sich vielmehr um einen Klassentyp, von dem alle Delegattypen abgeleitet werden.
21.3 Delegierte Mitglieder
Jeder Delegattyp erbt Member von der Klasse Delegate, siehe § 15.3.4. Darüber hinaus muss jeder Delegattyp eine nicht generische Invoke-Methode bereitstellen, deren Parameterliste der parameter_list in der Delegatdeklaration entspricht, deren Rückgabetyp dem return_type oder ref_return_type in der Delegatdeklaration entspricht, und für returns-by-ref-Delegate, deren ref_kind dem in der Delegatdeklaration entspricht. Die Methode Invoke muss mindestens die Zugriffsmöglichkeiten des enthaltenden Delegattyps bieten. Das Aufrufen der Invoke Methode für einen Delegatentyp entspricht semantisch der Verwendung der Stellvertretungsaufrufssyntax (§21.6).
Implementierungen können zusätzliche Member im Delegattyp definieren.
Abgesehen von der Instanziierung können alle Vorgänge, die auf eine Klasse oder Klasseninstanz angewendet werden können, auch auf eine Delegatklasse oder -instanz angewendet werden. Insbesondere kann über die übliche Memberzugriffssyntax auf Member des Typs System.Delegate zugegriffen werden.
21.4 Stellvertretungskompatibilität
Eine Methode oder ein Delegattyp (M) ist kompatibel mit einem Delegattyp (D), wenn alle folgenden Aussagen gelten:
-
DundMweisen dieselbe Anzahl an Parametern auf und jeder Parameter inDweist denselben als Referenz zu übergebenden Parametermodifizierer wie der entsprechende Parameter inMauf. - Für jeden Wertparameter ist eine Identitätskonvertierung (§ 10.2.2) oder eine implizite Verweiskonvertierung (§ 10.2.8) aus dem Parametertyp in
Din den entsprechenden Parametertyp inMvorhanden. - Für jeden als Referenz zu übergebenden Parameter ist der Parametertyp
Didentisch mit dem Parametertyp inM. - Eine der folgenden Bedingungen trifft zu:
-
DundMsind beide Rückgaben mit keinem Wert. -
DundMsind Rückgabe-nach-Wert (§15.6.1, §21.2), und eine Identitäts- oder implizite Verweiskonvertierung ist vom Rückgabetyp in den Rückgabetyp vonMD. -
DundMsind beide returns-by-ref, eine Identitätskonvertierung zwischen dem RückgabetypMund dem Rückgabetyp vonDexistiert, und beide haben dasselbe ref_kind.
-
Diese Definition der Kompatibilität ermöglicht die Kovarianz im Rückgabetyp und die 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.M1undB.M1sind mit den DelegattypenD1undD2kompatibel, da sie denselben Rückgabetyp und dieselbe Parameterliste verwenden. Die MethodenB.M2,B.M3undB.M4sind mit den DelegattypenD1undD2nicht kompatibel, da sie unterschiedliche Rückgabetypen oder Parameterlisten verwenden. Die MethodenB.M5undB.M6sind beide mit dem DelegattypD3kompatibel.Ende des Beispiels
Beispiel:
delegate bool Predicate<T>(T value); class X { static bool F(int i) {...} static bool G(string s) {...} }Die Methode
X.Fist mit dem DelegattypPredicate<int>kompatibel, und die MethodeX.Gist mit dem DelegattypPredicate<string>kompatibel.Ende des Beispiels
Hinweis: Die intuitive Bedeutung der Delegatkompatibilität besteht darin, dass eine Methode mit einem Delegattyp kompatibel ist, also jeder Aufruf des Delegaten durch einen Aufruf der Methode ersetzt werden kann, ohne die Typsicherheit zu verletzen. Optionale Parameter und Parameterarrays werden als explizite Parameter behandelt. Beachten Sie den folgenden Code als Beispiel:
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 Methode
Action<string>kompatibel, da jeder Aufruf einesAction<string>-Delegaten auch ein gültiger Aufruf der MethodeWenn die Signatur der oben genannten
Print(object value, bool prependTimestamp = false)geändert würde, wäre dieAction<string>kompatibel.Hinweisende
21.5 Instanziierung der Stellvertretung
Eine Instanz einer Stellvertretung wird durch eine delegate_creation_expression (§12.8.17.5), eine Konvertierung in einen Delegattyp, eine Stellvertretungskombination oder das Entfernen von Stellvertretungen erstellt. Die neu erstellte Delegatinstanz verweist dann auf eine oder mehrere der folgenden Komponenten:
- Die statische Methode, auf die in delegate_creation_expression verwiesen wird. Oder:
- Das Zielobjekt (darf nicht
nullsein) und die Instanzmethode, auf die in delegate_creation_expression verwiesen wird. Oder: - Ein anderer Stellvertreter (§12.8.17.5).
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 } }Ende des Beispiels
Der Satz der in einer Delegatinstanz gekapselten Methoden wird als Aufrufliste bezeichnet. Wenn eine Delegatinstanz aus einer einzelnen Methode erstellt wird, kapselt sie diese Methode, und ihre Aufrufliste enthält nur einen Eintrag. Wenn jedoch zwei Delegatinstanzen, bei denen es sich nicht um null handelt, kombiniert werden, werden ihre Aufruflisten in der Reihenfolge vom linken zum rechten Operanden verkettet. Dabei entsteht eine neue Aufrufliste, die zwei oder mehr Einträge enthält.
Wenn eine neue Stellvertretung aus einer einzigen Stellvertretung erstellt wird, hat die resultierende Aufrufliste nur einen Eintrag, bei dem es sich um den Quelldelegat (§12.8.17.5) handelt.
Stellvertretungen werden mit den binären + Operatoren (§12.12.5) und += Operatoren (§12.23.4) kombiniert. Eine Stellvertretung kann aus einer Kombination von Stellvertretungen mithilfe der binären - (§12.12.6) und -= operatoren (§12.23.4) entfernt werden. Stellvertretungen können für die Gleichheit verglichen werden (§12.14.9).
Beispiel: Das folgende Beispiel zeigt die Instanziierung einer Reihe von Delegaten und die 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
cd1undcd2instanziiert werden, kapseln sie jeweils eine Methode. Wenncd3instanziiert wird, enthält es eine Aufrufliste mit zwei Methoden:M1undM2(in dieser Reihenfolge). Die Aufrufliste voncd4enthältM1,M2undM1(in dieser Reihenfolge). Fürcd5enthält die AufruflisteM1,M2,M1,M1undM2(in dieser Reihenfolge).Beim Erstellen eines Delegaten mit delegate_creation_expression aus einem anderen Delegaten enthält das Ergebnis eine Aufrufliste mit einer anderen Struktur als beim Original, die jedoch dieselben Methoden in derselben Reihenfolge aufruft. Wenn
td3auscd3erstellt wird, enthält die Aufrufliste nur ein Member. Dieses Member ist dann aber eine Liste der MethodenM1undM2, und diese Methoden werden vontd3in derselben Reihenfolge wie voncd3aufgerufen. Entsprechend gilt, dass die Aufrufliste beim Instanziieren vontd4nur zwei Einträge enthält, aber die drei MethodenM1,M2undM1(in dieser Reihenfolge) genau wiecd4aufruft.Die Struktur der Aufrufliste wirkt sich auf die Delegatentfernung aus. Delegat
cd6, erstellt, indemcd2(ruftM2auf) voncd4(ruftM1,M2undM1auf) subtrahiert wird, ruftM1undM1auf. Delegattd6, erstellt, indemcd2(ruftM2auf) vontd4(ruftM1,M2undM1auf) subtrahiert wird, ruft weiterhinM1,M2undM1(in dieser Reihenfolge) auf, weilM2kein einzelner Eintrag in der Liste ist, sondern Member einer geschachtelten Liste. Weitere Beispiele für das Kombinieren (und Entfernen) von Delegaten finden Sie unter §21.6.Ende des Beispiels
Nach der Instanziierung verweist eine Delegatinstanz immer auf dieselbe Aufrufliste.
Hinweis: Bedenken Sie, dass beim Kombinieren zweier Delegate (oder beim Entfernen voneinander) ein neuer Delegat mit eigener Aufrufliste entsteht. Die Aufruflisten der beiden ursprünglichen Delegate bleiben unverändert. Hinweisende
21.6 Stellvertretungsaufruf
C# stellt eine spezielle Syntax für das Aufrufen von Delegaten bereit. Wenn eine Delegatinstanz, die nichtnull ist und deren Aufrufliste einen Eintrag enthält, aufgerufen wird, ruft sie die eine Methode mit denselben Argumenten auf, die ihr gegeben wurden, und gibt denselben Wert wie die referenzierte Methode zurück. (Ausführliche Informationen zum Aufruf von Delegaten finden Sie in §12.8.10.4.) Tritt während des Aufrufs eines solchen Delegaten eine Ausnahme auf und wird diese Ausnahme nicht innerhalb der aufgerufenen Methode abgefangen, 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 hätte, auf die sich dieser Delegat bezog.
Der Aufruf einer Delegat-Instanz, deren Aufrufliste mehrere Einträge enthält, wird fortgesetzt, indem jede der Methoden in der Aufrufliste synchron und in der angegebenen Reihenfolge aufgerufen wird. Jeder so aufgerufenen Methode wird dieselbe Gruppe von Argumenten übergeben, die auch an die Delegatinstanz übergeben wurde. Wenn ein solcher Delegataufruf Referenzparameter (§ 15.6.2.3.3) enthält, erfolgt jeder Methodenaufruf mit einem Verweis auf diese Variable. Änderungen an dieser Variablen durch eine Methode in der Aufrufliste sind für nachfolgende Methoden in der Aufrufliste sichtbar. Wenn der Delegataufruf Ausgabeparameter oder einen Rückgabewert enthält, stammt der endgültige Wert aus dem Aufruf des letzten Delegaten in der Liste. Tritt während der Verarbeitung des Aufrufs eines solchen Delegaten eine Ausnahme auf und wird diese Ausnahme nicht in der aufgerufenen Methode abgefangen, wird die Suche nach einer Ausnahme-Catch-Klausel in der Methode fortgesetzt, die den Delegaten aufgerufen hat. Die nachfolgenden Methoden in der Aufrufliste werden nicht aufgerufen.
Beim Versuch, eine Delegatinstanz aufzurufen, deren Wert null ist, tritt eine Ausnahme des Typs System.NullReferenceException auf.
Beispiel: Das folgende Beispiel zeigt, wie Delegate 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 ein Delegat mehrmals in einer Aufrufliste vorhanden sein. In diesem Fall wird er einfach einmal pro Vorkommen aufgerufen. Wenn der Delegat in einer Aufrufliste wie dieser entfernt wird, wird tatsächlich das letzte Vorkommen in der Aufrufliste entfernt.Unmittelbar vor der Ausführung der abschließenden Anweisung
cd3 -= cd1verweist der Delegatcd3auf eine leere Aufrufliste. Der Versuch, einen Delegaten aus einer leeren Liste zu entfernen (oder einen nicht vorhandenen Delegaten aus einer Liste zu entfernen, die nicht leer ist), verursacht keinen Fehler.Folgendes wird ausgegeben:
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: 60Ende des Beispiels
ECMA C# draft specification