Freigeben über


20 Stellvertretungen

20.1 Allgemein

Eine Delegatdeklaration definiert eine Klasse, die von der Klasse System.Delegateabgeleitet 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 publicModifizierer protected, , internalund 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 voidoder 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 und D2 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 und M die gleiche Anzahl von Parametern aufweisen, und jeder Parameter in D verfügt über denselben Zusatzparameter wie der entsprechende Parameter in M.
  • 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 in M.
  • Für jeden By-Reference-Parameter ist der Parametertyp D identisch mit dem Parametertyp in M.
  • Eine der folgenden Punkte ist wahr:
    • D und M sind beide Rückgaben ohne Wert.
    • D und M sind Rückgabe-nach-Wert (§15.6.1, §20.2), und eine Identitäts- oder implizite Verweiskonvertierung ist vom Rückgabetyp bis M zum Rückgabetyp von D.
    • D und M sind beide Rückgabe-by-Ref, eine Identitätskonvertierung ist zwischen dem Rückgabetyp M und dem Rückgabetyp von Dvorhanden, 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 sind B.M1 sowohl mit den Delegattypen D1 D2als auch kompatibel, da sie über denselben Rückgabetyp und dieselbe Parameterliste verfügen. Die Methoden B.M2, B.M3, und B.M4 sind mit den Delegattypen D1 nicht kompatibel, D2da sie unterschiedliche Rückgabetypen oder Parameterlisten haben. Die Methoden B.M5 und B.M6 sind beide mit dem Delegattyp D3kompatibel.

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 Delegattyp Predicate<int> kompatibel, und die Methode X.G ist mit dem Delegattyp Predicate<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 Print Methode ist mit dem Action<string> Delegattyp kompatibel, da jeder Aufruf eines Action<string> Delegaten auch ein gültiger Aufruf der Print Methode wäre.

Wenn die Signatur der Print oben genannten Methode beispielsweise geändert Print(object value, bool prependTimestamp = false) wurde, wäre die Print Methode nicht mehr mit Action<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 nullkann) 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 und cd2 instanziiert werden, kapseln sie jeweils eine Methode. Wenn cd3 instanziiert wird, enthält sie eine Aufrufliste mit zwei Methoden M1 und M2in dieser Reihenfolge. cd4Die Aufrufliste enthält M1, M2und , in M1dieser Reihenfolge. For cd5, the invocation list contains M1, , M2, M1, M1, and M2, 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 aus cd3 der Aufrufliste erstellt wird, hat nur ein Element, aber dieses Element ist eine Liste der Methoden M1 und M2 diese Methoden werden in derselben Reihenfolge aufgerufen, wie td3 sie von cd3ihnen aufgerufen werden. Ebenso, wenn td4 ihre Aufrufliste instanziiert wird, sind nur zwei Einträge vorhanden, aber sie ruft die drei Methoden M1, M2und M1in dieser Reihenfolge genauso auf cd4 .

Die Struktur der Aufrufliste wirkt sich auf die Subtraktion der Stellvertretung aus. Stellvertretung cd6, erstellt durch Subtrahieren cd2 (die M2aufgerufen wird ) von cd4 (die aufruft M1, M2und ) aufruft M1M1 und M1. Delegat td6, erstellt durch Subtrahieren cd2 (die aufgerufen wird ) von td4 (die M2aufruft M1M1, M2und M1) weiterhin aufgerufen wird, M2 und M1in dieser Reihenfolge, wie M2 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.NullReferenceExceptionfü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 -= cd1bezieht sich der Delegat cd3 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