Freigeben über


Partielle Ereignisse und Konstruktoren

Hinweis

Dieser Artikel ist eine Featurespezifikation. Die Spezifikation dient als Designdokument für das Feature. Es enthält vorgeschlagene Spezifikationsänderungen sowie Informationen, die während des Entwurfs und der Entwicklung des Features erforderlich sind. Diese Artikel werden veröffentlicht, bis die vorgeschlagenen Spezifikationsänderungen abgeschlossen und in die aktuelle ECMA-Spezifikation aufgenommen werden.

Es kann einige Abweichungen zwischen der Featurespezifikation und der abgeschlossenen Implementierung geben. Diese Unterschiede werden in den relevanten Sprachentwurfsbesprechungen (LDM)-Notizen erfasst.

Weitere Informationen zum Einführen von Featurespezifikationen in den C#-Sprachstandard finden Sie im Artikel zu den Spezifikationen.

Champion Issue: https://github.com/dotnet/csharplang/issues/9058

Zusammenfassung

Ermöglichen Sie dem partial Modifizierer für Ereignisse und Konstruktoren das Trennen von Deklarations- und Implementierungsteilen, ähnlich wie partielle Methoden und partielle Eigenschaften/Indexer.

partial class C
{
    partial C(int x, string y);
    partial event Action<int, string> MyEvent;
}

partial class C
{
    partial C(int x, string y) { }
    partial event Action<int, string> MyEvent
    {
        add { }
        remove { }
    }
}

Motivation

C# unterstützt bereits Partielle Methoden, Eigenschaften und Indexer. Partielle Ereignisse und Konstruktoren fehlen.

Teilereignisse eignen sich für schwache Ereignisbibliotheken , bei denen der Benutzer Definitionen schreiben könnte:

partial class C
{
    [WeakEvent]
    partial event Action<int, string> MyEvent;

    void M()
    {
        RaiseMyEvent(0, "a");
    }
}

Und ein vom Bibliothek bereitgestellter Quellgenerator würde Implementierungen bereitstellen:

partial class C
{
    private readonly WeakEvent _myEvent;

    partial event Action<int, string> MyEvent
    {
        add { _myEvent.Add(value); }
        remove { _myEvent.Remove(value); }
    }

    protected void RaiseMyEvent(int x, string y)
    {
        _myEvent.Invoke(x, y);
    }
}

Partielle Ereignisse und partielle Konstruktoren sind auch nützlich zum Generieren von Interopcode wie in Xamarin , in dem der Benutzer partielle Konstruktor- und Ereignisdefinitionen schreiben könnte:

partial class AVAudioCompressedBuffer : AVAudioBuffer
{
    [Export("initWithFormat:packetCapacity:")]
    public partial AVAudioCompressedBuffer(AVAudioFormat format, uint packetCapacity);

    [Export("create:")]
    public partial event EventHandler Created;
}

Und der Quellgenerator würde die Bindungen generieren (in diesem Fall zu Objective-C):

partial class AVAudioCompressedBuffer : AVAudioBuffer
{
    [BindingImpl(BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
    public partial AVAudioCompressedBuffer(AVAudioFormat format, uint packetCapacity) : base(NSObjectFlag.Empty)
    {
        // Call Objective-C runtime:
        InitializeHandle(
            global::ObjCRuntime.NativeHandle_objc_msgSendSuper_NativeHandle_UInt32(
                this.SuperHandle,
                Selector.GetHandle("initWithFormat:packetCapacity:"),
                format.GetNonNullHandle(nameof(format)),
                packetCapacity),
            "initWithFormat:packetCapacity:");
    }

    public partial event EventHandler Created
    {
        add { /* ... */ }
        remove { /* ... */ }
    }
}

Detailliertes Design

Allgemein

Die Ereignisdeklarationssyntax (§15.8.1) wird erweitert, um den partial Modifizierer zuzulassen:

 event_declaration
-    : attributes? event_modifier* 'event' type variable_declarators ';'
+    : attributes? event_modifier* 'partial'? 'event' type variable_declarators ';'
-    | attributes? event_modifier* 'event' type member_name
+    | attributes? event_modifier* 'partial'? 'event' type member_name
         '{' event_accessor_declarations '}'
     ;

Die Syntax der Instanzkonstruktordeklaration (§15.11.1) wird erweitert, um den partial Modifizierer zuzulassen:

 constructor_declaration
-    : attributes? constructor_modifier* constructor_declarator constructor_body
+    : attributes? constructor_modifier* 'partial'? constructor_declarator constructor_body
     ;

Beachten Sie, dass es einen Vorschlag gibt, den partial Modifizierer an beliebiger Stelle zwischen Modifizierern und nicht nur als letzte zuzulassen (auch für Methoden-, Eigenschafts- und Typdeklarationen).

Eine Ereignisdeklaration mit dem partial Modifizierer wird als partielle Ereignisdeklaration bezeichnet und ist einem oder mehreren Teilereignissen mit den angegebenen Namen zugeordnet (beachten Sie, dass eine Ereignisdeklaration ohne Accessoren mehrere Ereignisse definieren kann).

Eine Konstruktordeklaration mit dem partial Modifizierer wird als partielle Konstruktordeklaration bezeichnet und einem Teilkonstruktor mit der angegebenen Signatur zugeordnet.

Eine partielle Ereignisdeklaration wird als Implementierungsdeklaration bezeichnet, wenn sie den event_accessor_declarationsextern Modifizierer angibt. Andernfalls handelt es sich um eine definierende Deklaration.

Eine partielle Konstruktordeklaration wird als definierende Deklaration bezeichnet, wenn sie über einen Semikolontext verfügt und der extern Modifizierer fehlt. Andernfalls handelt es sich um eine Implementierungsdeklaration .

partial class C
{
    // defining declarations
    partial C();
    partial C(int x);
    partial event Action E, F;

    // implementing declarations
    partial C() { }
    partial C(int x) { }
    partial event Action E { add { } remove { } }
    partial event Action F { add { } remove { } }
}

Nur die definierende Deklaration eines Teilmitglieds nimmt an der Suche teil und wird bei Verwendungswebsites und zum Ausstellen der Metadaten berücksichtigt. (Mit Ausnahme von Dokumentationskommentaren wie unten beschrieben.) Die implementierende Deklarationssignatur wird zur Null-Analyse der zugehörigen Körper verwendet.

Ein partielles Ereignis oder ein Konstruktor:

  • Darf nur als Mitglied eines Teiltyps deklariert werden.
  • Muss eine definierende und eine Implementierungsdeklaration haben.
  • Darf den abstract Modifizierer nicht haben.
  • Ein Schnittstellenmememm kann nicht explizit implementiert werden.

Ein Teilereignis ist nicht feldähnliche (§15.8.2), d. h.:

  • Es verfügt nicht über einen vom Compiler generierten Sicherungsspeicher oder Accessoren.
  • Sie kann nur in += und -= Vorgängen verwendet werden, nicht als Wert.

Eine definierende partielle Konstruktordeklaration darf keinen Konstruktorinitialisierer (: this() oder : base(); §15.11.2).

Analyseunterbrechung

Das Zulassen des partial Modifizierers in mehr Kontexten ist eine bahnbrechende Änderung:

class C
{
    partial F() => new partial(); // previously a method, now a constructor
    @partial F() => new partial(); // workaround to keep it a method
}

class partial { }

Um den Sprachparser zu vereinfachen, wird der partial Modifizierer bei jeder methodenähnlichen Deklaration (d. h. lokalen Funktionen und Skriptmethoden auf oberster Ebene) akzeptiert, auch wenn wir die oben angegebenen Grammatikänderungen nicht explizit angeben.

Attribute

Die Attribute des resultierenden Ereignisses oder Konstruktors sind die kombinierten Attribute der partiellen Deklarationen an den entsprechenden Positionen. Die kombinierten Attribute werden in einer nicht angegebenen Reihenfolge verkettet, und Duplikate werden nicht entfernt.

Die methodattribute_target (§22.3) wird für Partielle Ereignisdeklarationen ignoriert. Accessorattribute werden nur von Accessordeklarationen verwendet (die nur unter der Implementierungsdeklaration vorhanden sein können). Beachten Sie, dass param und returnattribute_targetbereits für alle Ereignisdeklarationen ignoriert werden.

Caller-Info-Attribute für die Implementierungsdeklaration werden vom Compiler ignoriert, wie durch den Teileigenschaftenvorschlag im Abschnitt Caller-Info-Attribute angegeben (beachten Sie, dass es für alle partiellen Member gilt, die Partielle Ereignisse und Konstruktoren enthalten).

Signaturen

Beide Deklarationen eines partiellen Elements müssen übereinstimmende Signaturen aufweisen, die teileigenschaften ähneln:

  1. Typen- und Referenztypenunterschiede zwischen partiellen Deklarationen, die für die Laufzeit erheblich sind, führen zu einem Kompilierungszeitfehler.
  2. Unterschiede bei Tupelelementnamen zwischen partiellen Deklarationen führen zu einem Kompilierungsfehler.
  3. Die Deklarationen müssen dieselben Modifizierer aufweisen, obwohl die Modifizierer möglicherweise in einer anderen Reihenfolge angezeigt werden.
    • Ausnahme: Dies gilt nicht für den Modifizierer, der extern nur in der Implementierungsdeklaration angezeigt werden kann.
  4. Alle anderen syntaktischen Unterschiede in den Signaturen partieller Deklarationen führen zu einer Kompilierungszeitwarnung mit den folgenden Ausnahmen:
    • Attributlisten müssen nicht wie oben beschrieben übereinstimmen.
    • Nullable Kontextunterschiede (z. B. oblivious vs. annotated) verursachen keine Warnungen.
    • Standardparameterwerte müssen nicht übereinstimmen, aber eine Warnung wird gemeldet, wenn die implementierende Konstruktordeklaration Standardwerte enthält (da diese ignoriert werden, da nur die definierende Deklaration an der Suche teilnimmt).
  5. Eine Warnung tritt auf, wenn Parameternamen sich beim Definieren und Implementieren von Konstruktordeklarationen unterscheiden.
  6. Nullbarkeitsunterschiede, die keine verschleierte Nullbarkeit erfordern, führen zu Warnungen.

Dokumentationskommentare

Es ist zulässig, Dokumentkommentare sowohl zur Definition als auch zur Implementierung der Deklaration aufzunehmen. Beachten Sie, dass Dokumentkommentare für Ereignisaccessoren nicht unterstützt werden.

Wenn Dokumentkommentare nur für eine der Deklarationen eines Teilelements vorhanden sind, werden diese Dokumentkommentare normal verwendet (dies wird über Roslyn-APIs angezeigt, die an die XML-Dokumentationsdatei ausgegeben werden).

Wenn Dokumentkommentare für beide Deklarationen eines Teilmitglieds vorhanden sind, werden alle Dokumentkommentare zur definierenden Deklaration gelöscht, und nur die Dokumentkommentare zur Implementierungsdeklaration werden verwendet.

Wenn Parameternamen zwischen Deklarationen eines partiellen Elements abweichen, paramref verwenden Elemente die Parameternamen aus der Deklaration, die dem Dokumentationskommentar im Quellcode zugeordnet ist. Beispielsweise bezieht sich ein paramref in einem Dokumentkommentar platzierter Dokumentkommentar auf die Parametersymbole der implementierenden Deklaration mithilfe ihrer Parameternamen. Dies kann verwirrend sein, da die Metadatensignatur Parameternamen aus der definierenden Deklaration verwendet. Es wird empfohlen, sicherzustellen, dass Parameternamen in den Deklarationen eines partiellen Elements übereinstimmen, um diese Verwirrung zu vermeiden.

Offene Fragen

Elementtypen

Möchten wir Teilereignisse, Konstruktoren, Operatoren, Felder? Wir schlagen die ersten beiden Mitgliedstypen vor, aber jede andere Teilmenge könnte berücksichtigt werden.

Partielle primäre Konstruktoren können auch berücksichtigt werden, z. B. die Möglichkeit, dass der Benutzer dieselbe Parameterliste für mehrere partielle Typdeklarationen hat.

Attributspeicherorte

Sollten wir den [method:] Attributzielbezeichner für Teilereignisse (oder nur die definierenden Deklarationen) erkennen? Dann wären die resultierenden Accessorattribute die Verkettung von method-Targeting-Attributen aus beiden (oder nur den definierenden) Deklarationsteilen sowie Self-Targeting- und method-Targeting-Attributen von den Accessoren der Implementierungsdeklaration. Das Kombinieren von Attributen aus verschiedenen Deklarationsarten wäre beispiellos, und die aktuelle Implementierung der Attributabgleiche in Roslyn unterstützt dies nicht.

Wir können auch die Anerkennung von [param:] und [return:] in Betracht ziehen, nicht nur bei Teilereignissen, sondern bei allen feldähnlichen und externen Ereignissen. Weitere Details finden Sie unter https://github.com/dotnet/roslyn/issues/77254.