Partiella händelser och konstruktorer

Anmärkning

Den här artikeln är en funktionsspecifikation. Specifikationen fungerar som designdokument för funktionen. Den innehåller föreslagna specifikationsändringar, tillsammans med information som behövs under utformningen och utvecklingen av funktionen. Dessa artiklar publiceras tills de föreslagna specifikationsändringarna har slutförts och införlivats i den aktuella ECMA-specifikationen.

Det kan finnas vissa skillnader mellan funktionsspecifikationen och den slutförda implementeringen. Dessa skillnader samlas in i de relevanta LDM-anteckningarna (Language Design Meeting).

Du kan lära dig mer om processen för att införa funktionsspecifikationer i C#-språkstandarden i artikeln om specifikationerna.

Champion-fråga: https://github.com/dotnet/csharplang/issues/9058

Sammanfattning

partial Tillåt att modifieraren för händelser och konstruktorer separerar deklarations- och implementeringsdelar, liknande partiella metoder och partiella egenskaper/indexerare.

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# stöder redan partiella metoder, egenskaper och indexerare. Partiella händelser och konstruktorer saknas.

Partiella händelser skulle vara användbara för svaga händelsebibliotek , där användaren kan skriva definitioner:

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

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

Och en biblioteksbaserad källgenerator skulle tillhandahålla implementeringar:

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);
    }
}

Partiella händelser och partiella konstruktorer skulle också vara användbara för att generera interopkod som i Xamarin där användaren kan skriva partiella konstruktor- och händelsedefinitioner:

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

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

Och källgeneratorn skulle generera bindningarna (till Objective-C i det här fallet):

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 { /* ... */ }
    }
}

Detaljerad design

Allmänt

Händelsedeklarationssyntaxen (§15.8.1) utökas för att tillåta modifieraren partial :

 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 '}'
     ;

Instanskonstruktordeklarationssyntax (§15.11.1) utökas för att tillåta partial modifieraren:

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

Observera att det finns ett förslag om att tillåta partial modifieraren var som helst bland modifierare, i stället för bara som den sista (även för metod-, egenskaps- och typdeklarationer).

En händelsedeklaration med partial modifieraren sägs vara en partiell händelsedeklaration och den är associerad med en eller flera partiella händelser med de angivna namnen (observera att en händelsedeklaration utan åtkomst kan definiera flera händelser).

En konstruktordeklaration med partial modifieraren sägs vara en partiell konstruktordeklaration och den är associerad med en partiell konstruktor med den angivna signaturen.

En partiell händelsedeklaration sägs vara en implementeringsdeklarationevent_accessor_declarations när den anger eller har extern modifieraren. Annars är det en definierande deklaration.

En partiell konstruktordeklaration sägs vara en definierande förklaring när den har en semikolonkropp och saknar extern modifieraren. Annars är det en implementerade deklaration.

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 { } }
}

Endast den definierande deklarationen för en partiell medlem deltar i uppslag och beaktas på användningsplatser och för att generera metadata. (Förutom dokumentationskommentar enligt beskrivningen nedan.) Signaturen för genomförandedeklarationen används för nullbar analys av de associerade organen.

En partiell händelse eller konstruktor:

  • Kan endast deklareras som medlem av en partiell typ.
  • Måste ha en definierande och en implementeringsdeklaration.
  • Tillåts inte att ha abstract modifieraren.
  • Det går inte att uttryckligen implementera en gränssnittsmedlem.

En partiell händelse är inte fältliknande (§15.8.2), dvs.

  • Den har inte någon stödlagring eller åtkomst som genereras av kompilatorn.
  • Den kan bara användas i += och -= åtgärder, inte som ett värde.

En definierande partiell konstruktordeklaration kan inte ha en konstruktorinitierare (: this() eller : base(); §15.11.2).

Parsningsbrytning

partial Att tillåta modifieraren i fler kontexter är en icke-bakåtkompatibel ändring:

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

class partial { }

För att förenkla språkparsern partial godkänns modifieraren i valfri metodliknande deklaration (dvs. lokala funktioner och skriptmetoder på den översta nivån), även om vi inte uttryckligen anger grammatikändringarna ovan.

Egenskaper

Attributen för den resulterande händelsen eller konstruktorn är de kombinerade attributen för de partiella deklarationerna i motsvarande positioner. De kombinerade attributen sammanfogas i en ospecificerad ordning och dubbletter tas inte bort.

method (§22.3) ignoreras vid partiella händelsedeklarationer. Accessor-attribut används endast från åtkomstdeklarationer (som endast kan finnas i implementeringsdeklarationen). Observera att param och returnattribute_targets redan ignoreras i alla händelsedeklarationer.

Anropar-info-attribut i implementeringsdeklarationen ignoreras av kompilatorn enligt förslaget om partiella egenskaper i avsnittet Caller-info-attribut (observera att den gäller för alla partiella medlemmar som innehåller partiella händelser och konstruktorer).

Signaturer

Båda deklarationerna för en partiell medlem måste ha matchande signaturer som liknar partiella egenskaper:

  1. Typ- och referenstypskillnader mellan partiella deklarationer som är viktiga för körningen resulterar i ett kompileringsfel.
  2. Skillnader i tuppelns elementnamn mellan partiella deklarationer resulterar i ett kompileringsfel.
  3. Deklarationerna måste ha samma modifierare, även om modifierarna kan visas i en annan ordning.
    • Undantag: Detta gäller inte för modifieraren extern som endast får visas i implementeringsdeklarationen.
  4. Alla andra syntaktiska skillnader i signaturer för partiella deklarationer resulterar i en kompileringstidsvarning med följande undantag:
    • Attributlistor behöver inte matchas enligt beskrivningen ovan.
    • Skillnader i nullable-kontext (såsom omedvetna vs. annoterade) orsakar inte varningar.
    • Standardparametervärdena behöver inte matcha, men en varning rapporteras när implementeringskonstruktordeklarationen har standardparametervärden (eftersom dessa skulle ignoreras eftersom endast den definierande deklarationen deltar i uppslag).
  5. En varning inträffar när parameternamn skiljer sig åt mellan att definiera och implementera konstruktordeklarationer.
  6. Nullabilitetsskillnader som inte innebär omedveten nullabilitet resulterar i varningar.

Dokumentationskommentar

Det är tillåtet att inkludera dokumentkommentar om både den definierande och implementerande deklarationen. Observera att dokumentkommentarer inte stöds för händelseåtkomster.

När dokumentkommenterar endast finns på en av deklarationerna för en partiell medlem används dessa dokumentkommentare normalt (visas via Roslyn-API:er, som skickas till XML-dokumentationsfilen).

När dokumentkommenterar finns på båda deklarationerna av en partiell medlem, tas alla dokumentkommentare på den definierande deklarationen bort och endast dokumentkommentarna i implementeringsdeklarationen används.

När parameternamn skiljer sig mellan deklarationer för en partiell medlem paramref använder elementen parameternamnen från deklarationen som är associerad med dokumentationskommentatorn i källkoden. paramref En i en dokumentkommentare som placerats på en implementeringsdeklaration refererar till parametersymbolerna för implementeringsdeklarationen med hjälp av deras parameternamn. Det kan vara förvirrande eftersom metadatasignaturen använder parameternamn från den definierande deklarationen. Vi rekommenderar att du ser till att parameternamnen matchar deklarationerna för en partiell medlem för att undvika den här förvirringen.

Öppna frågor

Medlemstyper

Vill vi ha partiella händelser, konstruktorer, operatorer, fält? Vi föreslår de två första medlemstyperna, men alla andra delmängder kan övervägas.

Partiella primära konstruktorer kan också övervägas, t.ex. tillåta användaren att ha samma parameterlista på flera partiella typdeklarationer.

Attributplatser

Ska vi identifiera [method:] attributmålsspecificeraren för partiella händelser (eller bara de definierande deklarationerna)? Sedan skulle de resulterande åtkomstattributen vara sammanlänkningen av method-mål-attribut från båda (eller bara de definierande) deklarationsdelarna, plus självriktande och method-mål-attribut från åtkomsterna i implementeringsdeklarationen. Att kombinera attribut från olika typer av deklarationer skulle vara utan motstycke och den aktuella implementeringen av attributmatchning i Roslyn stöder inte detta.

Vi kan också överväga att känna igen [param:] och [return:], inte bara vid partiella händelser, utan vid alla fältliknande och externa händelser. Mer information finns i https://github.com/dotnet/roslyn/issues/77254.