Dela via


Utvidgningsmedlemmar

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/8697

Deklaration

Syntax

class_body
    : '{' class_member_declaration* '}' ';'?
    | ';'
    ;

class_member_declaration
    : constant_declaration
    | field_declaration
    | method_declaration
    | property_declaration
    | event_declaration
    | indexer_declaration
    | operator_declaration
    | constructor_declaration
    | finalizer_declaration
    | static_constructor_declaration
    | type_declaration
    | extension_declaration // add
    ;

extension_declaration // add
    : 'extension' type_parameter_list? '(' receiver_parameter ')' type_parameter_constraints_clause* extension_body
    ;

extension_body // add
    : '{' extension_member_declaration* '}' ';'?
    ;

extension_member_declaration // add
    : method_declaration
    | property_declaration
    | operator_declaration
    ;

receiver_parameter // add
    : attributes? parameter_modifiers? type identifier?
    ;

Tilläggsdeklarationer ska endast deklareras i icke-generiska, icke-kapslade statiska klasser.
Det är ett fel att en typ namnges extension.

Omfångsregler

Typparametrarna och mottagarparametern för en tilläggsdeklaration är inom räckvidden i tilläggsdeklarationens kropp. Det är ett fel att referera till mottagarparametern inifrån en statisk medlem, förutom i ett nameof uttryck. Det är ett fel när medlemmar deklarerar typparametrar eller parametrar (samt lokala variabler och lokala funktioner direkt i medlemmens kropp) med samma namn som en typparameter eller mottagarparameter i en tilläggsmetoddeklaration.

public static class E
{
    extension<T>(T[] ts)
    {
        public bool M1(T t) => ts.Contains(t);        // `T` and `ts` are in scope
        public static bool M2(T t) => ts.Contains(t); // Error: Cannot refer to `ts` from static context
        public void M3(int T, string ts) { }          // Error: Cannot reuse names `T` and `ts`
        public void M4<T, ts>(string s) { }           // Error: Cannot reuse names `T` and `ts`
    }
}

Det är inte ett fel att medlemmarna själva har samma namn som typparametrarna eller mottagarparametern för den omslutande tilläggsdeklarationen. Medlemsnamn hittas inte direkt i ett enkelt namnsökning från tilläggsdeklarationen. sökningen hittar därför typparametern eller mottagarparametern för det namnet i stället för medlemmen.

Medlemmar ger upphov till statiska metoder som deklareras direkt på den omslutande statiska klassen, och de kan hittas via enkel namnsökning; En tilläggsdeklarationstypparameter eller mottagarparameter med samma namn hittas dock först.

public static class E
{
    extension<T>(T[] ts)
    {
        public void T() { M(ts); } // Generated static method M<T>(T[]) is found
        public void M() { T(ts); } // Error: T is a type parameter
    }
}

Statiska klasser som tilläggscontainrar

Tillägg deklareras i icke-generiska statiska klasser på toppnivå, precis som tilläggsmetoder i dag, och kan därför samexistera med klassiska tilläggsmetoder och statiska medlemmar som inte är tillägg:

public static class Enumerable
{
    // New extension declaration
    extension(IEnumerable source) { ... }
    
    // Classic extension method
    public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source) { ... }
    
    // Non-extension member
    public static IEnumerable<int> Range(int start, int count) { ... } 
}

Tilläggsdeklarationer

En tilläggsdeklaration är anonym och tillhandahåller en mottagarspecifikation med eventuella associerade typparametrar och begränsningar, följt av en uppsättning medlemsdeklarationer för tillägg. Mottagarspecifikationen kan vara i form av en parameter, eller - om endast statiska tilläggsmedlemmar deklareras - en typ:

public static class Enumerable
{
    extension(IEnumerable source) // extension members for IEnumerable
    {
        public bool IsEmpty { get { ... } }
    }
    extension<TSource>(IEnumerable<TSource> source) // extension members for IEnumerable<TSource>
    {
        public IEnumerable<T> Where(Func<TSource, bool> predicate) { ... }
        public IEnumerable<TResult> Select<TResult>(Func<TSource, TResult> selector) { ... }
    }
    extension<TElement>(IEnumerable<TElement>) // static extension members for IEnumerable<TElement>
        where TElement : INumber<TElement>
    {
        public static IEnumerable<TElement> operator +(IEnumerable<TElement> first, IEnumerable<TElement> second) { ... }
    }
}

Typen i mottagarspecifikationen kallas mottagartyp och parameternamnet, om det finns, kallas mottagarparametern.

Om mottagarparametern heter kanske mottagartypen inte är statisk.
Mottagarparametern får inte ha modifierare om den inte är namngiven, och det är endast tillåtet att ha refnessmodifierarna listade nedan och scoped på annat sätt.
Mottagarparametern har samma begränsningar som den första parametern för en klassisk tilläggsmetod.
Attributet [EnumeratorCancellation] ignoreras om det placeras på mottagarparametern.

Utvidgningsmedlemmar

Medlemsdeklarationer för tillägg är syntaktiskt identiska med motsvarande instans och statiska medlemmar i klass- och structdeklarationer (med undantag för konstruktorer). Instansmedlemmar refererar till mottagaren med mottagarens parameternamn:

public static class Enumerable
{
    extension(IEnumerable source)
    {
        // 'source' refers to receiver
        public bool IsEmpty => !source.GetEnumerator().MoveNext();
    }
}

Det är ett fel att ange en instanstilläggsmedlem om den omslutande tilläggsdeklarationen inte anger någon mottagarparameter:

public static class Enumerable
{
    extension(IEnumerable) // No parameter name
    {
        public bool IsEmpty => true; // Error: instance extension member not allowed
    }
}

Det är ett fel att ange följande modifierare för en medlem i en tilläggsdeklaration: abstract, virtual, override, new, sealed, partial och protected (och relaterade åtkomstmodifierare).
Egenskaper i tilläggsdeklarationer får inte ha init åtkomst.
Instansmedlemmarna tillåts inte om mottagarparametern är namnlös.

Alla medlemmar ska ha namn som skiljer sig från namnet på den statiska omslutande klassen och namnet på den utökade typen om den har en.

Det är ett fel att dekorera en tilläggsmedlem med attributet [ModuleInitializer] .

Refness

Som standard skickas mottagaren till instanstilläggsmedlemmar efter värde, precis som andra parametrar. En tilläggsdeklarationsmottagare i parameterformulär kan dock ange ref, ref readonly och in, så länge mottagartypen är känd för att vara en värdetyp.

Om ref anges kan en instansmedlem eller någon av dess åtkomstmottagare deklareras readonly, vilket förhindrar att mottagaren muteras:

public static class Bits
{
    extension(ref ulong bits) // receiver is passed by ref
    {
        public bool this[int index]
        {
            set => bits = value ? bits | Mask(index) : bits & ~Mask(index); // mutates receiver
            readonly get => (bits & Mask(index)) != 0;                // cannot mutate receiver
        }
    }
    static ulong Mask(int index) => 1ul << index;
}

Nullbarhet och attribut

Mottagartyper kan vara eller innehålla null-referenstyper, och mottagarspecifikationer som är i form av parametrar kan ange attribut:

public static class NullableExtensions
{
    extension(string? text)
    {
        public string AsNotNull => text is null ? "" : text;
    }
    extension([NotNullWhen(false)] string? text)
    {
        public bool IsNullOrEmpty => text is null or [];
    }
    extension<T> ([NotNull] T t) where T : class?
    {
        public void ThrowIfNull() => ArgumentNullException.ThrowIfNull(t);
    }
}

Kompatibilitet med klassiska tilläggsmetoder

Metoder för instanstillägg genererar artefakter som matchar dem som skapas av klassiska tilläggsmetoder.

Specifikt har den genererade statiska metoden attribut, modifierare och namn på den deklarerade tilläggsmetoden, samt typparameterlistan, parameterlistan och begränsningslistan sammanfogade från tilläggsdeklarationen och metoddeklarationen i den ordningen:

public static class Enumerable
{
    extension<TSource>(IEnumerable<TSource> source) // Generate compatible extension methods
    {
        public IEnumerable<TSource> Where(Func<TSource, bool> predicate) { ... }
        public IEnumerable<TSource> Select<TResult>(Func<TSource, TResult> selector)  { ... }
    }
}

Genererar:

[Extension]
public static class Enumerable
{
    [Extension]
    public static IEnumerable<TSource> Where<TSource>(IEnumerable<TSource> source, Func<TSource, bool> predicate) { ... }

    [Extension]
    public static IEnumerable<TSource> Select<TSource, TResult>(IEnumerable<TSource> source, Func<TSource, TResult> selector)  { ... }
}

Operatörer

Även om tilläggsoperatorer har explicita operandtyper måste de fortfarande deklareras i en tilläggsdeklaration:

public static class Enumerable
{
    extension<TElement>(IEnumerable<TElement>) where TElement : INumber<TElement>
    {
        public static IEnumerable<TElement> operator *(IEnumerable<TElement> vector, TElement scalar) { ... }
        public static IEnumerable<TElement> operator *(TElement scalar, IEnumerable<TElement> vector) { ... }
    }
}

Detta gör att typparametrar kan deklareras och härledas och motsvarar hur en vanlig användardefinierad operator måste deklareras inom någon av dess operandtyper.

Kontrollera

Slutsatsbarhet: För varje icke-metodtilläggsmedlem måste alla typparametrar i dess tilläggsblock användas i den kombinerade uppsättningen parametrar från tillägget och medlemmen.

Unikhet: Inom en viss omslutande statisk klass behandlas uppsättningen med tilläggsmedlemsdeklarationer med samma mottagartyp (modulo-identitetskonvertering och typparameternamnsersättning) som ett enda deklarationsutrymme som liknar medlemmarna i en klass- eller structdeklaration och omfattas av samma regler om unikhet.

public static class MyExtensions
{
    extension<T1>(IEnumerable<int>) // Error! T1 not inferrable
    {
        ...
    }
    extension<T2>(IEnumerable<T2>)
    {
        public bool IsEmpty { get ... }
    }
    extension<T3>(IEnumerable<T3>?)
    {
        public bool IsEmpty { get ... } // Error! Duplicate declaration
    }
}

Tillämpningen av den här unikhetsregeln innehåller klassiska tilläggsmetoder i samma statiska klass. För jämförelse med metoder i tilläggsdeklarationer behandlas parametern this som en mottagarspecifikation tillsammans med alla typparametrar som anges i den mottagartypen, och de återstående typparametrarna och metodparametrarna används för metodsignaturen:

public static class Enumerable
{
    public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source) { ... }
    
    extension(IEnumerable source) 
    {
        IEnumerable<TResult> Cast<TResult>() { ... } // Error! Duplicate declaration
    }
}

Förbrukning

När en sökning efter en tilläggsmedlem görs, bidrar alla tilläggsdeklarationer i statiska klasser som har using-importerats med sina medlemmar som kandidater, oavsett mottagartyp. Endast som en del av lösningen tas kandidater med inkompatibla mottagartyper bort.
En fullständig generell typinferens görs mellan typen av argument (inklusive den faktiska mottagaren) och alla typparametrar (som kombinerar dem i tilläggsdeklarationen och i tilläggsmedlemsdeklarationen).
När explicita typargument anges används de för att ersätta typparametrarna för tilläggsdeklarationen och medlemsdeklarationen för tillägget.

string[] strings = ...;

var query = strings.Select(s => s.Length); // extension invocation
var query2 = strings.Select<string, int>(s => s.Length); // ... with explicit full set of type arguments

var query3 = Enumerable.Select(strings, s => s.Length); // static method invocation
var query4 = Enumerable.Where<string, int>(strings, s => s.Length); // ... with explicit full set of type arguments
 
public static class Enumerable
{
    extension<TSource>(IEnumerable<TSource> source)
    {
        public IEnumerable<TResult> Select<TResult>(Func<T, TResult> predicate) { ... }
    }
}

På samma sätt som med klassiska tilläggsmetoder kan de avgivna implementeringsmetoderna anropas statiskt.
Detta gör det möjligt för kompilatorn att skilja mellan tilläggsmedlemmar med samma namn och aritet.

object.M(); // ambiguous
E1.M();

new object().M2(); // ambiguous
E1.M2(new object());

_ = _new object().P; // ambiguous
_ = E1.get_P(new object());

static class E1
{
    extension(object)
    {
        public static void M() { }
        public void M2() { }
        public int P => 42;
    }
}

static class E2
{
    extension(object)
    {
        public static void M() { }
        public void M2() { }
        public int P => 42;
    }
}

Statiska utökande metoder kommer att lösas som instansutökande metoder (vi kommer att överväga ett extra argument av typ mottagare).
Tilläggsegenskaper kommer att hanteras som tilläggsmetod, med en enda parameter (mottagarparametern) och ett enda argument (det faktiska mottagarvärdet).

using static Direktiv

En using_static_directive gör medlemmar i tilläggsblock i typdeklarationen tillgängliga för tilläggsåtkomst.

using static N.E;

new object().M();
object.M2();

_ = new object().Property;
_ = object.Property2;

C c = null;
_ = c + c;
c += 1;

namespace N
{
    static class E
    {
        extension(object o)
        {
            public void M() { }
            public static void M2() { }
            public int Property => 0;
            public static int Property2 => 0;
        }

        extension(C c)
        {
            public static C operator +(C c1, C c2) => throw null;
            public void operator +=(int i) => throw null;
        }
    }
}

class C { } 

Precis som tidigare kan tillgängliga statiska medlemmar (utom tilläggsmetoder) som finns direkt i deklarationen av den angivna typen refereras direkt.
Det innebär att implementeringsmetoder (förutom de som är tilläggsmetoder) kan användas direkt som statiska metoder:

using static E;

M();
System.Console.Write(get_P());
set_P(43);
_ = op_Addition(0, 0);
_ = new object() + new object();

static class E
{
    extension(object)
    {
        public static void M() { }
        public static int P { get => 42; set { } }
        public static object operator +(object o1, object o2) { return o1; }
    }
}

En using_static_directive importerar fortfarande inte tilläggsmetoder direkt som statiska metoder, så implementeringsmetoden för icke-statiska tilläggsmetoder kan inte anropas direkt som en statisk metod.

using static E;

M(1); // error: The name 'M' does not exist in the current context

static class E
{
    extension(int i)
    {
        public void M() { }
    }
}

Overlastupplösningsprioritetsattribut

Tilläggsmedlemmar i en omslutande statisk klass prioriteras enligt ORPA-värden. Den omslutande statiska klassen anses vara den "innehållande typen" som beaktas av ORPA-reglerna.
Alla ORPA-attribut som finns på en tilläggsegenskap kopieras till implementeringsmetoderna för egenskapens accessorer, så att prioriteringen respekteras när dessa accessorer används via disambiguationssyntax.

Startpunkter

Metoder för tilläggsblock kvalificerar sig inte som startpunktskandidater (se "7.1 Programstart"). Obs! Implementeringsmetoden kan fortfarande vara en kandidat.

Sänka

Sänkningsstrategin för tilläggsdeklarationer är inte ett beslut på språknivå. Förutom att implementera språksemantiken måste det dock uppfylla vissa krav:

  • Formatet för genererade typer, medlemmar och metadata bör anges tydligt i alla fall så att andra kompilatorer kan använda och generera det.
  • De genererade artefakterna bör vara stabila, i den meningen att rimliga senare ändringar inte ska påverka användare som kompilerats mot tidigare versioner.

Dessa krav behöver förfining när genomförandet fortskrider och kan behöva äventyras i hörnfall för att möjliggöra en rimlig genomförandemetod.

Metadata för deklarationer

Mål

Designen nedan tillåter:

  • avrundning av tilläggsdeklarationssymboler via metadata (fullständiga och referenssammansättningar),
  • stabila referenser till tilläggsmedlemmar (xml-dokument),
  • lokal bestämning av utgivna namn (till hjälp för EnC),
  • spårning av offentliga API:er.

För XML-dokument är docID för en tilläggsmedlem densamma som docID för den i metadata. Det docID som används i cref="Extension.extension(object).M(int)" är M:Extension.<>E__ExtensionGroupingTypeNameForObject.M(System.Int32) till exempel och att docID är stabilt för omkompileringar och ombeställningar av tilläggsblock. Helst skulle det också förbli stabilt när begränsningar för tilläggsblocket ändras, men vi hittade ingen design som kunde uppnå det utan negativ effekt på språkdesignen vid konflikter mellan medlemmar.

För EnC är det användbart att veta lokalt (bara genom att titta på en modifierad tilläggsmedlem) var den uppdaterade tilläggsmedlemmen genereras i metadata.

För offentlig API-spårning minskar mer stabila namn bruset. Men tekniskt sett bör namnen på typer av tilläggsgrupperingar inte spela in i sådana scenarier. När du tittar på tilläggsmedlem Mspelar det ingen roll vad som är namnet på tilläggsgrupperingstypen, det viktiga är signaturen för tilläggsblocket som det tillhör. Den offentliga API-signaturen bör inte ses som Extension.<>E__ExtensionGroupingTypeNameForObject.M(System.Int32) utan snarare som Extension.extension(object).M(int). Med andra ord bör tilläggsmedlemmar anses ha två uppsättningar av typparametrar och två uppsättningar parametrar.

Översikt

Tilläggsblock grupperas efter signaturen på CLR-nivå. Varje CLR-likvärdighetsgrupp genereras som en tilläggsgruppstyp med ett innehållsbaserat namn. Tilläggsblock i en CLR-likvärdighetsgrupp grupperas sedan efter C#-ekvivalens. Varje C#-ekvivalensgrupp genereras som en tilläggsmarkörtyp med ett innehållsbaserat namn, kapslat i motsvarande tilläggsgruppstyp. En tilläggsmarkörtyp innehåller en enda tilläggsmarkörmetod som kodar en tilläggsparameter. Tilläggsmarkörmetoden med sin inkluderande tilläggsmarkörtyp kodar signaturen för ett tilläggsblock med fullständig precision. Deklarationen för varje tilläggsmedlem genereras i rätt tilläggsgruppstyp, refererar tillbaka till en tilläggsmarkörtyp med dess namn via ett attribut och åtföljs av en statisk implementeringsmetod på den översta nivån med en modifierad signatur.

Här är en schematiserad översikt över metadatakodning:

[Extension]
static class EnclosingStaticClass
{
    [Extension]
    public sealed class ExtensionGroupingType1 // has type parameters with minimal constraints sufficient to keep extension member declarations below valid
    {
        public static class ExtensionMarkerType1 // has re-declared type parameters with full fidelity of C# constraints
        {
            public static void <Extension>$(... extension parameter ...) // extension marker method
        }
        ... ExtensionMarkerType2, etc ...

        ... extension members for ExtensionGroupingType1, each points to its corresponding extension marker type ...
    }

    ... ExtensionGroupingType2, etc ...

    ... implementation methods ...
}

Den omslutande statiska klassen genereras med ett [Extension] attribut.

SIGNATUR PÅ CLR-nivå jämfört med C#-nivåsignatur

Signaturen på CLR-nivå för ett tilläggsblock är resultatet av:

  • normalisera typparameternamn till T0, T1osv .
  • ta bort attribut
  • radera parameternamnet
  • radera parametermodifierare (till exempel ref, in, scoped, ...)
  • radera tuppelnamn
  • radera nullabilitetsanteckningar
  • ta bort notnull begränsningar

Obs! Andra begränsningar bevaras, till exempel new(), struct, class, allows ref struct, unmanagedoch typbegränsningar.

Grupperingstyper för tillägg

En tilläggsgrupperingstyp uttrycks i metadata för varje uppsättning tilläggsblock i källkoden med samma CLR-nivå signatur.

  • Namnet är outsägligt och som bestäms av innehållet i signaturen på CLR-nivå. Mer information finns nedan.
  • Dess typparametrar har normaliserade namn (T0, T1, ...) och har inga attribut.
  • Det är offentligt och förseglat.
  • Den är markerad med specialname flaggan och ett [Extension] attribut.

Det innehållsbaserade namnet på tilläggsgrupperingstypen baseras på signaturen på CLR-nivå och innehåller följande:

  • Det fullständigt kvalificerade CLR-namnet för typen av tilläggsparameter.
    • Refererade typparameternamn normaliseras till T0, T1osv . baserat på vilken ordning de visas i typdeklarationen.
    • Det fullständigt kvalificerade namnet innehåller inte den innehållande sammansättningen. Det är vanligt att typer flyttas mellan sammansättningar och att de inte ska bryta xml-dokumentreferenserna.
  • Begränsningar av typparametrar inkluderas och sorteras så att omordningen av dem i källkoden inte ändrar namnet. Specifikt:
    • Typparameterbegränsningar visas i deklarationsordning. Begränsningarna för parametern Nth type inträffar före parametern Nth+1-typ.
    • Typbegränsningar kommer att sorteras genom att de fullständiga namnen jämförs i ordningsföljd.
    • Icke-typbegränsningar ordnas deterministiskt och hanteras för att undvika tvetydighet eller kollision med typbegränsningar.
  • Eftersom detta inte innehåller attribut ignorerar det avsiktligt specifika C#-konventioner som tupelnamn, nullegenskaper med mera.

Obs! Namnet garanteras förblir stabilt vid omkompileringar, omstruktureringar och ändringar av C#-specifika särdrag (det vill säga sådana som inte påverkar signaturen på CLR-nivå).

Tilläggsmarkörtyper

Markörtypen deklarerar på nytt typparametrarna för dess innehållande grupperingstyp (en utökningsgrupperingstyp) för att erhålla fullständig trohet mot C#-vyn av utökningsblock.

En tilläggsmarkörtyp skickas till metadata för varje uppsättning tilläggsblock i källan med samma C#-nivåsignatur.

  • Namnet är outtalbart och bestäms baserat på innehållet i signaturen på C#-nivå för tilläggsblocket. Mer information finns nedan.
  • Den omdeklarerar typparametrarna för dess innehållande grupperingstyp till dem som är deklarerade i källan (inklusive namn och attribut).
  • Det är offentligt och statiskt.
  • Den är markerad med specialname flaggan.

Det innehållsbaserade namnet på tilläggsmarkörtypen baseras på följande:

  • Namnen på typparametrarna inkluderas i den ordning de visas i tilläggsdeklarationen
  • Attributen för typparametrar inkluderas och sorteras så att omordningen av dem i källkoden inte ändrar namnet.
  • Begränsningar av typparametrar inkluderas och sorteras så att omordningen av dem i källkoden inte ändrar namnet.
  • Det fullständigt kvalificerade C#-namnet för den utökade typen
    • Detta kommer att innehålla poster som nullable-anteckningar, tuppelnamn, och så vidare ...
    • Det fullständigt kvalificerade namnet innehåller inte den innehållande sammansättningen
  • Namnet på tilläggsparametern
  • Modifierarna för tilläggsparametern (ref, ref readonly, scoped, ...) i en deterministisk ordning
  • De fullständigt kvalificerade namn- och attributargumenten för alla attribut som tillämpas på tilläggsparametern i en deterministisk ordning

Obs! Namnet är garanterat stabilt för omkompilering och ombeställningar.
Observera: Tilläggsmarkörtyper och tilläggsmarkörmetoder emitteras som en del av referenssammansättningar.

Tilläggsmarkörmetod

Syftet med markörmetoden är att koda tilläggsparametern för tilläggsblocket. Eftersom den är medlem i tilläggsmarkörtypen kan den referera till de återförklarade typparametrarna för tilläggsmarkörtypen.

Varje typ av tilläggsmarkör innehåller en enda metod, tilläggsmarkörmetoden.

  • Det är statiskt, icke-generiskt, void-returning och kallas <Extension>$.
  • Denna enda parameter har attributen, referenskaraktär, typ och namn från tilläggsparametern.
    Om tilläggsparametern inte anger något namn är parameternamnet tomt.
  • Den är markerad med specialname flaggan.

Tillgängligheten för markörmetoden är den minst restriktiva tillgängligheten bland motsvarande deklarerade tilläggsmedlemmar, om ingen deklareras används private.

Utvidgningsmedlemmar

Metod-/egenskapsdeklarationer i ett tilläggsblock i källan representeras som medlemmar av tilläggsgrupperingens typ i metadata.

  • Signaturerna för de ursprungliga metoderna behålls (inklusive attribut), men deras kroppar ersätts av throw NotImplementedException().
  • Dessa bör inte refereras i IL.
  • Metoder, egenskaper och deras accessorer markeras med [ExtensionMarkerName("...")] som hänvisar till namnet på typen av tilläggsmarkör som motsvarar det tilläggsblock som hör till den medlemmen.

Implementeringsmetoder

Metodorganen för metod-/egenskapsdeklarationer i ett tilläggsblock i källan genereras som statiska implementeringsmetoder i den statiska klassen på den översta nivån.

  • En implementeringsmetod har samma namn som den ursprungliga metoden.
  • Den har typparametrar som härleds från tilläggsblocket som har lagts till i typparametrarna för den ursprungliga metoden (inklusive attribut).
  • Den har samma hjälpmedel och attribut som den ursprungliga metoden.
  • Om den implementerar en statisk metod har den samma parametrar och returtyp.
  • Om den implementerar en instansmetod har den en förberedd parameter till signaturen för den ursprungliga metoden. Den här parameterns attribut, refness, typ och namn härleds från tilläggsparametern som deklareras i relevant tilläggsblock.
  • Parametrarna i implementeringsmetoderna refererar till typparametrar som ägs av implementeringsmetoden i stället för de som tillhör ett tilläggsblock.
  • Om den ursprungliga medlemmen är en vanlig instansmetod markeras implementeringsmetoden med ett [Extension] attribut.

Attributet ExtensionMarkerName

Den ExtensionMarkerNameAttribute typen är endast för kompilatoranvändning – den är inte tillåten i källan. Typdeklarationen syntetiseras av kompilatorn om den inte redan ingår i kompilatorn.

namespace System.Runtime.CompilerServices;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)]
public sealed class ExtensionMarkerNameAttribute : Attribute
{
    public ExtensionMarkerNameAttribute(string name)
        => Name = name;

    public string Name { get; }
}

Obs! Även om vissa attributmål ingår för framtidssäkring (kapslade tilläggstyper, tilläggsfält, tilläggshändelser), ingår inte AttributeTargets.Constructor eftersom tilläggskonstruktorer inte skulle klassificeras som konstruktörer.

Exempel

Obs! Vi använder förenklade innehållsbaserade namn för exemplet för läsbarhet. Obs! Eftersom C# inte kan representera typparameterns återdeklaration är koden som representerar metadata inte giltig C#-kod.

Här är ett exempel som illustrerar hur gruppering fungerar, utan medlemmar:

class E
{
    extension<T>(IEnumerable<T> source)
    {
        ... member in extension<T>(IEnumerable<T> source)
    }

    extension<U>(ref IEnumerable<U?> p)
    {
        ... member in extension<U>(ref IEnumerable<U?> p)
    }

    extension<T>(IEnumerable<U> source)
        where T : IEquatable<U>
    {
        ... member in extension<T>(IEnumerable<U> source) where T : IEquatable<U>
    }
}

emitteras som

[Extension]
class E
{
    [Extension, SpecialName]
    public sealed class <>E__ContentName_For_IEnumerable_T<T0>
    {
        [SpecialName]
        public static class <>E__ContentName1 // note: re-declares type parameter T0 as T
        {
            [SpecialName]
            public static void <Extension>$(IEnumerable<T> source) { }
        }

        [SpecialName]
        public static class <>E__ContentName2 // note: re-declares type parameter T0 as U
        {
            [SpecialName]
            public static void <Extension>$(ref IEnumerable<U?> p) { }
        }

        [ExtensionMarkerName("<>E__ContentName1")]
        ... member in extension<T>(IEnumerable<T> source)

        [ExtensionMarkerName("<>E__ContentName2")]
        ... member in extension<U>(ref IEnumerable<U?> p)
    }

    [Extension, SpecialName]
    public sealed class <>ContentName_For_IEnumerable_T_With_Constraint<T0>
       where T0 : IEquatable<T0>
    {
        [SpecialName]
        public static class <>E__ContentName3 // note: re-declares type parameter T0 as U
        {
            [SpecialName]
            public static void <Extension>$(IEnumerable<U> source) { }
        }

        [ExtensionMarkerName("ContentName3")]
        public static bool IsPresent(U value) => throw null!;
    }

    ... implementation methods
}

Här är ett exempel som illustrerar hur medlemmar avsänds:

static class IEnumerableExtensions
{
    extension<T>(IEnumerable<T> source) where T : notnull
    {
        public void Method() { ... }
        internal static int Property { get => ...; set => ...; }
        public int Property2 { get => ...; set => ...; }
    }

    extension(IAsyncEnumerable<int> values)
    {
        public async Task<int> SumAsync() { ... }
    }

    public static void Method2() { ... }
}

emitteras som

[Extension]
static class IEnumerableExtensions
{
    [Extension, SpecialName]
    public sealed class <>E__ContentName_For_IEnumerable_T<T0>
    {
        // Extension marker type is emitted as a nested type and re-declares its type parameters to include C#-isms
        // In this example, the type parameter `T0` is re-declared as `T` with a `notnull` constraint:
        // .class <>E__IEnumerableOfT<T>.<>E__ContentName_For_IEnumerable_T_Source
        // .typeparam T
        //     .custom instance void NullableAttribute::.ctor(uint8) = (...)
        [SpecialName]
        public static class <>E__ContentName_For_IEnumerable_T_Source
        {
            [SpecialName]
            public static <Extension>$(IEnumerable<T> source) => throw null;
        }

        [ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
        public void Method() => throw null;

        [ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
        internal static int Property
        {
            [ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
            get => throw null;
            [ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
            set => throw null;
        }

        [ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
        public int Property2
        {
            [ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
            get => throw null;
            [ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
            set => throw null;
        }
    }

    [Extension, SpecialName]
    public sealed class <>E__ContentName_For_IAsyncEnumerable_Int
    {
        [SpecialName]
        public static class <>E__ContentName_For_IAsyncEnumerable_Int_Values
        {
            [SpecialName]
            public static <Extension>$(IAsyncEnumerable<int> values) => throw null;
        }

        [ExtensionMarkerName("<>E__ContentName_For_IAsyncEnumerable_Int_Values")]
        public Task<int> SumAsync() => throw null;
    }

    // Implementation for Method
    [Extension]
    public static void Method<T>(IEnumerable<T> source) { ... }

    // Implementation for Property
    internal static int get_Property<T>() { ... }
    internal static void set_Property<T>(int value) { ... }

    // Implementation for Property2
    public static int get_Property2<T>(IEnumerable<T> source) { ... }
    public static void set_Property2<T>(IEnumerable<T> source, int value) { ... }

    // Implementation for SumAsync
    [Extension]
    public static int SumAsync(IAsyncEnumerable<int> values) { ... }

    public static void Method2() { ... }
}

När tilläggsmedlemmar används i källan, ger vi ut dem som en referens till implementeringsmetoder. Till exempel: ett anrop av enumerableOfInt.Method() skulle genereras som ett statiskt anrop till IEnumerableExtensions.Method<int>(enumerableOfInt).

XML-dokument

Dokumentkommentarerna om tilläggsblocket genereras för markörtypen (DocID är E.<>E__MarkerContentName_For_ExtensionOfT'1 för tilläggsblocket i exemplet nedan).
De kan referera till tilläggsparametern och typparametrarna med respektive <paramref><typeparamref> ).
Obs! Du kanske inte dokumenterar tilläggsparametern eller typparametrarna (med <param> och <typeparam>) på en tilläggsmedlem.

Om två tilläggsblock emitteras som en markörtyp sammanfogas även deras dokumentkommentarer.

Verktyg som använder XML-dokumenten ansvarar för att kopiera <param> och <typeparam> från tilläggsblocket till tilläggsmedlemmarna efter behov, det vill säga att parameterinformationen bör endast kopieras för instansmedlemmar.

En <inheritdoc> emitteras om implementeringsmetoder och hänvisar till relevant tilläggsmedlem med en cref. Implementeringsmetoden för en getter refererar till exempel till dokumentationen för tilläggsegenskapen. Om tilläggsmedlemmen inte har dokumentationskommentarer utelämnas <inheritdoc>.

För tilläggsblock och tilläggsmedlemmar varnar vi för närvarande inte om:

  • tilläggsparametern är dokumenterad, men parametrarna på medlemmen i tillägget är inte dokumenterade
  • eller vice versa
  • eller i motsvarande scenarier med odokumenterade typparametrar

Följande dokument kommenterar till exempel:

/// <summary>Summary for E</summary>
static class E
{
    /// <summary>Summary for extension block</summary>
    /// <typeparam name="T">Description for T</typeparam>
    /// <param name="t">Description for t</param>
    extension<T>(T t)
    {
        /// <summary>Summary for M, which may refer to <paramref name="t"/> and <typeparamref name="T"/></summary>
        /// <typeparam name="U">Description for U</typeparam>
        /// <param name="u">Description for u</param>
        public void M<U>(U u) => throw null!;

        /// <summary>Summary for P</summary>
        public int P => 0;
    }
}

ger följande xml:

<?xml version="1.0"?>
<doc>
    <assembly>
        <name>Test</name>
    </assembly>
    <members>
        <member name="T:E">
            <summary>Summary for E</summary>
        </member>
        <member name="T:E.&lt;&gt;E__MarkerContentName_For_ExtensionOfT`1">
            <summary>Summary for extension block</summary>
            <typeparam name="T">Description for T</typeparam>
            <param name="t">Description for t</param>
        </member>
        <member name="M:E.&lt;&gt;E__MarkerContentName_For_ExtensionOfT`1.M``1(``0)">
            <summary>Summary for M, which may refer to <paramref name="t"/> and <typeparamref name="T"/></summary>
            <typeparam name="U">Description for U</typeparam>
            <param name="u">Description for u</param>
        </member>
        <member name="P:E.&lt;&gt;E__MarkerContentName_For_ExtensionOfT`1.P">
            <summary>Summary for P</summary>
        </member>
        <member name="M:E.M``2(``0,``1)">
            <inheritdoc cref="M:E.&lt;&gt;E__MarkerContentName_For_ExtensionOfT`1.M``1(``0)"/>
        </member>
        <member name="M:E.get_P``1(``0)">
            <inheritdoc cref="P:E.&lt;&gt;E__MarkerContentName_For_ExtensionOfT`1.P"/>
        </member>
    </members>
</doc>

CREF-referenser

Vi kan behandla tilläggsblock som kapslade typer som kan hanteras av deras signatur (som om det vore en metod med en enda tilläggsparameter). Exempel: E.extension(ref int).M().

Men en cref kan inte hantera själva tilläggsblocket. E.extension(int) kan referera till en metod med namnet "extension" i typen E.

static class E
{
  extension(ref int i)
  {
    void M() { } // can be addressed by cref="E.extension(ref int).M()" or cref="extension(ref int).M()" within E, but not cref="M()"
  }
  extension(ref  int i)
  {
    void M(int i2) { } // can be addressed by cref="E.extension(ref int).M(int)" or cref="extension(ref int).M(int)" within E
  }
}

Sökningen vet att leta i alla matchande utvidgningsblock.
Eftersom vi inte tillåter okvalificerade hänvisningar till tilläggsmedlemmar skulle cref också inte tillåta dem.

Syntaxen skulle vara:

member_cref
  : conversion_operator_member_cref
  | extension_member_cref // added
  | indexer_member_cref
  | name_member_cref
  | operator_member_cref
  ;

extension_member_cref // added
 : 'extension' type_argument_list? cref_parameter_list '.' member_cref
 ;

qualified_cref
  : type '.' member_cref
  ;

cref
  : member_cref
  | qualified_cref
  | type_cref
  ;

Det är ett fel att använda extension_member_cref på toppnivå (extension(int).M) eller kapslad i ett annat tillägg (E.extension(int).extension(string).M).

Brytande förändringar

Typer och alias får inte ha namnet "extension".

Öppna problem

Tillfälligt avsnitt i dokumentet som rör öppna problem, inklusive diskussion om oavslutad syntax och alternativa design
  • Ska vi justera mottagarkraven när vi får åtkomst till en tilläggsmedlem? (kommentar)
  • Bekräfta extension jämfört med extensions nyckelordet (svar: extension, LDM 2025-03-24)
  • Bekräfta att vi inte vill tillåta [ModuleInitializer] (svar: ja, tillåt inte, LDM 2025-06-11)
  • Bekräfta att vi är okej med att kassera tilläggsblock som kandidater för startpunkt (svar: ja, kassera, LDM 2025-06-11)
  • Bekräfta LangVer-logik (hoppa över nya tillägg jämfört med att överväga och rapportera dem när de väljs) (svar: binda villkorslöst och rapportera LangVer-fel förutom instanstilläggsmetoder, LDM 2025-06-11)
  • Ska partial krävas för tilläggsblock som sammanfogas och deras dokumentkommentarer slås samman? (svar: dokumentkommentarer sammanfogas tyst när block slås samman, behövs inte partial , bekräftas via e-post 2025-09-03)
  • Bekräfta att medlemmar inte ska namnges efter de innehållande eller utökade typerna. (svar: ja, bekräftat via e-post 2025-09-03)

Gå tillbaka till grupperings-/konfliktregler mot bakgrund av portabilitetsproblem: https://github.com/dotnet/roslyn/issues/79043

(svar: det här scenariot löstes som en del av ny metadatadesign med innehållsbaserade typnamn, det är tillåtet)

Den aktuella logiken är att gruppera tilläggsblock som har samma mottagartyp. Detta tar inte hänsyn till begränsningar. Detta orsakar ett portabilitetsproblem med det här scenariot:

static class E
{
   extension<T>(ref T) where T : struct
      void M()
   extension<T>(T) where T : class
      void M()
}

Förslaget är att använda samma grupperingslogik som vi planerar för designen av tilläggsgrupperingstypen, nämligen för att ta hänsyn till begränsningar på CLR-nivå (dvs. ignorera notnull, tupplar, nullability-anteckningar).

Ska refness kodas i grupperingens typnamn?

  • Granska förslag som ref inte ingår i namn på tilläggsgrupperingstyp (behöver diskuteras ytterligare efter att WG återbesöker grupperings-/konfliktregler, LDM 2025-06-23) (svar: bekräftat via e-post 2025-09-03)
public static class E
{
  extension(ref int)
  {
    public static void M()
  }
}

Den genereras som:

public static class E
{
  public static class <>ExtensionTypeXYZ
  {
    .. marker method ...
    void M()
  }
}

Och CREF-referens från tredje part för E.extension(ref int).M genereras som M:E.<>ExtensionGroupingTypeXYZ.M() Om ref tas bort eller läggs till i en tilläggsparameter vill vi förmodligen inte att CREF ska brytas.

Vi bryr oss inte så mycket om det här scenariot, eftersom all användning som tillägg skulle vara en tvetydighet:

public static class E
{
  extension(ref int)
    static void M()
  extension(int)
    static void M()
}

Men vi bryr oss om det här scenariot (för portabilitet och användbarhet), och detta bör fungera med föreslagen metadatadesign när vi har justerat konfliktreglerna:

static class E
{
   extension<T>(ref T) where T : struct
      void M()
   extension<T>(T) where T : class
      void M()
}

Att inte redovisa refness har en nackdel, eftersom vi förlorar portabilitet i det här scenariot:

static class E
{
   extension<T>(ref T)
      void M()
   extension<T>(T)
      void M()
}
// portability issue: since we're grouping without accounting for refness, the emitted extension members conflict (not implementation members). Mitigation: keep as classic extensions or split to another static class

namn

  • Ska vi förbjuda tilläggsegenskaper i nameof precis som vi gör med klassiska och nya tilläggsmetoder? (svar: vi vill använda "nameof(EnclosingStaticClass.ExtensionMember). Behöver design, kommer troligen att skjutas upp från .NET 10. LDM 2025-06-11)

mönsterbaserade konstruktioner

Metoder

  • Var ska nya tilläggsmetoder komma in? (svar: samma platser där klassiska tilläggsmetoder spelar in, LDM 2025-05-05)

Detta omfattar:

  • GetEnumerator / GetAsyncEnumerator i foreach
  • Deconstruct i dekonstruktion, i positionsmönster och "foreach"
  • Add i insamlingsinitierare
  • GetPinnableReference i fixed
  • GetAwaiter i await

Detta utesluter:

  • Dispose / DisposeAsync i using och foreach
  • MoveNext / MoveNextAsync i foreach
  • Slice och int indexerare i implicita indexerare (och eventuellt listmönster?)
  • GetResult i await

Egenskaper och indexerare

  • Var ska tilläggsegenskaper och indexerare spela in? (svar: låt oss börja med de fyra, LDM 2025-05-05)

Vi skulle inkludera:

  • objektinitierare: new C() { ExtensionProperty = ... }
  • ordlistainitierare: new C() { [0] = ... }
  • with: x with { ExtensionProperty = ... }
  • egenskapsmönster: x is { ExtensionProperty: ... }

Vi utesluter:

  • Current i foreach
  • IsCompleted i await
  • Count / Length egenskaper och indexerare i listmönster
  • Count / Length egenskaper och indexerare i implicita indexerare
Egenskaper som returneras av ombud
  • Bekräfta att tilläggsegenskaperna för den här formen endast ska spela in i LINQ-frågor för att matcha vad instansegenskaper gör. (svar: är vettigt, LDM 2025-04-06)
List- och spridningsmönster
  • Bekräfta att tilläggsindexerare Index/Range bör spela upp i listmönster (svar: inte relevant för C# 14)
Se över var Count/Length tilläggsegenskaperna spelar in

Samlingsuttryck

  • Tillägget Add fungerar
  • Tillägget GetEnumerator fungerar för spridning
  • Tillägget GetEnumerator påverkar inte bestämningen av elementtypen (måste vara instans)
  • Statiska Create tilläggsmetoder bör inte räknas som en välsignad skapandemetod
  • Ska tilläggsegenskaper som kan räknas påverka samlingsuttryck?

params samlingar

  • Add Tillägg påverkar inte vilka typer som tillåts medparams

ordlisteuttryck

  • Bekräfta att tilläggsindexerare inte används i ordbokuttryck, eftersom indexerarens närvaro är en integrerad del av vad som definierar en ordbokstyp. (svar: inte relevant för C# 14)

extern

Namngivnings-/numreringsschema för tilläggstyp

Fråga
Det aktuella numreringssystemet orsakar problem med valideringen av offentliga API:er , vilket säkerställer att offentliga API:er matchar mellan endast referenssammansättningar och implementeringssammansättningar.

Ska vi göra någon av följande ändringar? (svar: vi antar ett innehållsbaserat namngivningsschema för att öka stabiliteten i det offentliga API:et, och verktygen måste fortfarande uppdateras för att ta hänsyn till markörmetoder)

  1. justera verktyget
  2. använda något innehållsbaserat namngivningsschema (TBD)
  3. låt namnet kontrolleras via en viss syntax

Den nya generiska tilläggsmetoden Cast kan fortfarande inte fungera i LINQ

Fråga
I tidigare utformningar av roller/tillägg var det möjligt att endast ange metodens typargument explicit.
Men nu när vi fokuserar på en verkalös övergång från klassiska tilläggsmetoder måste alla typargument anges explicit.
Detta kan inte åtgärda ett problem med tilläggsanvändningen för Cast-metoden i LINQ.

Ska vi göra en ändring i tilläggsfunktionen för att hantera det här scenariot? (svar: nej, detta leder inte till att vi går tillbaka till utformningen av utbyggnadsupplösningen, LDM 2025-05-05)

Begränsa tilläggsparametern för en tilläggsmedlem

Ska vi tillåta följande? (svar: nej, detta kan läggas till senare)

static class E
{
    extension<T>(T t)
    {
        public void M<U>(U u) where T : C<U>  { } // error: 'E.extension<T>(T).M<U>(U)' does not define type parameter 'T'
    }
}

public class C<T> { }

Nullbarhet

  • Bekräfta den aktuella designen, dvs. maximal portabilitet/kompatibilitet (svar: ja, LDM 2025-04-17)
    extension([System.Diagnostics.CodeAnalysis.DoesNotReturnIf(false)] bool b)
    {
        public void AssertTrue() => throw null!;
    }
    extension([System.Diagnostics.CodeAnalysis.NotNullIfNotNull("o")] ref int? i)
    {
        public void M(object? o)  => throw null!;
    }

Metainformation

  • Ska skelettmetoder kasta NotSupportedException eller någon annan standardutlösning (för närvarande gör vi det med throw null;)? (svar: ja, LDM 2025-04-17)
  • Ska vi acceptera mer än en parameter i markörmetoden i metadata (om nya versioner lägger till mer information)? (svar: vi kan förbli strikta, LDM 2025-04-17)
  • Ska tilläggsmarkören eller talbara implementeringsmetoder markeras med specialnamn? (svar: markörmetoden bör markeras med specialnamn och vi bör kontrollera den, men inte implementeringsmetoder, LDM 2025-04-17)
  • Ska vi lägga till [Extension] attribut i den statiska klassen även om det inte finns någon instanstilläggsmetod inuti? (svar: ja, LDM 2025-03-10)
  • Bekräfta att vi också bör lägga till [Extension] attributet för implementeringens getters och setters. (svar: nej, LDM 2025-03-10)
  • Bekräfta att tilläggstyperna ska markeras med specialnamn och att kompilatorn kräver den här flaggan i metadata (detta är en icke-bakåtkompatibel ändring från förhandsversionen) (svar: godkänd, LDM 2025-06-23)

scenario med statisk fabrik

  • Vilka är konfliktreglerna för statiska metoder? (svar: använd befintliga C#-regler för den omslutande statiska typen, ingen avkoppling, LDM 2025-03-17)

Sökning

  • Hur löser jag anrop av instansmetoder nu när vi har talarbara implementeringsnamn? Vi föredrar skelettmetoden jämfört med motsvarande implementeringsmetod.
  • Hur löser jag metoder för statiska tillägg? (svar: precis som metoder för instansförlängning, LDM 2025-03-03)
  • Så här hanterar du egenskaper? (besvaras i stora drag LDM 2025-03-03, men behöver uppföljning för förbättring)
  • Omfångs- och skuggningsregler för tilläggsparametrar och typparametrar (svar: i omfånget av tilläggsblocket, skuggning otillåten, LDM 2025-03-10)
  • Hur ska ORPA gälla för nya tilläggsmetoder? (svar: behandla tilläggsblock som transparenta, "innehållande typ" för ORPA är den omslutande statiska klassen, LDM 2025-04-17)
public static class Extensions
{
    extension(Type1)
    {
        [OverloadResolutionPriority(1)]
        public void Overload(...)
    }
    extension(Type2)
    {
        public void Overload(...)
    }
}
  • Ska ORPA gälla för nya tilläggsegenskaper? (svar: ja och ORPA bör kopieras till implementeringsmetoder, LDM 2025-04-23)
public static class Extensions
{
    extension(int[] i)
    {
        public P { get => }
    }
    extension(ReadOnlySpan<int> r)
    {
       [OverloadResolutionPriority(1)]
       public P { get => }
    }
}
  • Hur återbegränsar du de klassiska lösningsreglerna för tillägg? Gör vi det
    1. uppdatera standarden för klassiska tilläggsmetoder och använd den för att även beskriva nya tilläggsmetoder,
    2. behålla det befintliga språket för klassiska tilläggsmetoder, använd det för att även beskriva nya tilläggsmetoder, men har en känd specifikationsavvikelse för båda,
    3. behålla det befintliga språket för klassiska tilläggsmetoder, men använda ett annat språk för nya tilläggsmetoder och bara ha en känd specifikationsavvikelse för klassiska tilläggsmetoder?
  • Bekräfta att vi inte vill tillåta explicita typargument för en egenskapsåtkomst (svar: ingen egenskapsåtkomst med explicita typargument som beskrivs i WG)
string s = "ran";
_ = s.P<object>; // error

static class E
{
    extension<T>(T t)
    {
        public int P => 0;
    }
}
  • Bekräfta att vi vill att förbättringsregler ska tillämpas även när mottagaren är en typ (svar: en typ-enbart utvidgningsparameter bör övervägas vid lösningen av statiska extension-medlemmar, LDM 2025-06-23)
int.M();

static class E1
{
    extension(int)
    {
        public static void M() { }
    }
}
static class E2
{
    extension(in int i)
    {
        public static void M() => throw null;
    }
}
  • Bekräfta att vi är okej med att ha en tvetydighet när både metoder och egenskaper är tillämpliga (svar: vi bör utforma ett förslag för att göra bättre än nuvarande situation, skjuta upp från .NET 10, LDM 2025-06-23)
  • Bekräfta att vi inte vill ha några förbättringar för alla medlemmar innan vi fastställer den vinnande medlemstypen (svar: punting ut ur .NET 10, WG 2025-07-02)
string s = null;
s.M(); // error

static class E
{
    extension(string s)
    {
        public System.Action M => throw null;
    }
    extension(object o)
    {
        public string M() => throw null;
    }
}
  • Har vi en implicit mottagare i tilläggsdeklarationer? (svar: nej, diskuterades tidigare i LDM)
static class E
{
    extension(object o)
    {
        public void M() 
        {
            M2();
        }
        public void M2() { }
    }
}
  • Ska vi tillåta sökning efter typparameter? (diskussion) (svar: nej, vi väntar på feedback, LDM 2025-04-16)

Tillgänglighet

  • Vad är innebörden av tillgänglighet i en tilläggsdeklaration? (svar: tilläggsdeklarationer räknas inte som ett tillgänglighetsomfång, LDM 2025-03-17)
  • Ska vi tillämpa kontrollen "inkonsekvent tillgänglighet" på mottagarparametern även för statiska medlemmar? (svar: ja, LDM 2025-04-17)
public static class Extensions
{
    extension(PrivateType p)
    {
        // We report inconsistent accessibility error, 
        //   because we generate a `public static void M(PrivateType p)` implementation in enclosing type
        public void M() { } 

        public static void M2() { } // should we also report here, even though not technically necessary?
    }

    private class PrivateType { }
}

Validering av tilläggsdeklaration

  • Ska vi lätta på valideringen av typparametern (slutsatsbarhet: alla typparametrar måste visas i typen för tilläggsparametern) där det bara finns metoder? (svar: ja, LDM 2025-04-06) Detta skulle möjliggöra överföring av 100% av klassiska förlängningsmetoder.
    Om du har TResult M<TResult, TSource>(this TSource source)kan du porta den som extension<TResult, TSource>(TSource source) { TResult M() ... }.

  • Bekräfta huruvida init-only-accessorer ska tillåtas i tillägg (svar: okej att inte tillåta för närvarande, LDM 2025-04-17)

  • Ska denna enda skillnad i mottagarens referenslighet tillåtas extension(int receiver) { public void M2() {} }extension(ref int receiver) { public void M2() {} }? (svar: nej, behåll specifikationsregeln, LDM 2025-03-24)

  • Ska vi klaga på en konflikt som den här extension(object receiver) { public int P1 => 1; }extension(object receiver) { public int P1 {set{}} }? (svar: ja, behåll specifikationsregeln, LDM 2025-03-24)

  • Ska vi klaga på konflikter mellan skelettmetoder som inte är konflikter mellan implementeringsmetoder? (svar: ja, behåll specifikationsregeln, LDM 2025-03-24)

static class E
{
    extension(object)
    {
        public void Method() {  }
        public static void Method() { }
    }
}

De aktuella konfliktreglerna är: 1. kontrollera att det inte finns någon konflikt i liknande tillägg med hjälp av regler för klass/struct, 2. kontrollera att det inte finns någon konflikt mellan implementeringsmetoder i olika tilläggsdeklarationer.

  • Behöver vi den första delen av reglerna? (svar: Ja, vi behåller den här strukturen eftersom den hjälper till med förbrukningen av API:erna, LDM 2025-03-24)

XML-dokument

  • Stöds paramref mottagarparametern för tilläggsmedlemmar? Även på statisk elektricitet? Hur kodas den i utdata? Förmodligen skulle standardsätt <paramref name="..."/> fungera för en människa, men det finns en risk att vissa befintliga verktyg inte gärna hittar det bland parametrarna i API:et. (svar: ja paramref till tilläggsparameter tillåts för tilläggsmedlemmar, LDM 2025-05-05)
  • Ska vi kopiera dokumentkommentar till implementeringsmetoderna med talbara namn? (svar: ingen kopiering, LDM 2025-05-05)
  • Ska <param>-elementet som motsvarar mottagarens parameter kopieras från tilläggscontainern för instansmetoder? Något annat bör kopieras från container till implementeringsmetoder (<typeparam> etc.) ? (svar: ingen kopiering, LDM 2025-05-05)
  • Bör <param> tilläggsparameter tillåtas för tilläggsmedlemmar som en åsidosättning? (svar: nej, för tillfället, LDM 2025-05-05)
  • Kommer sammanfattningen av tilläggsblock att visas var som helst?

CREF

  • Bekräfta syntax (svar: förslaget är bra, LDM 2025-06-09)
  • Bör det vara möjligt att referera till ett tilläggsblock (E.extension(int))? (svar: nej, LDM 2025-06-09)
  • Ska det vara möjligt att referera till en medlem med en okvalificerad syntax: extension(int).Member? (svar: ja, LDM 2025-06-09)
  • Ska vi använda olika tecken för ett outtalbart namn för att undvika XML-escaping? (svar: överlåta till WG, LDM 2025-06-09)
  • Bekräfta att det är okej att både referenser till skelett- och implementeringsmetoder är möjliga: E.M jämfört med E.extension(int).M. Båda verkar nödvändiga (tilläggsegenskaper och portabilitet för klassiska tilläggsmetoder). (svar: ja, LDM 2025-06-09)
  • Är namn på tilläggsmetadata problematiska för versionsdokument? (svar: Ja, vi kommer att gå bort från ordningstal och använda ett innehållsbaserat stabilt namngivningsschema)

Lägga till stöd för fler medlemstyper

Vi behöver inte implementera all den här designen samtidigt, men kan närma oss den en eller några medlemstyper i taget. Baserat på kända scenarier i våra kärnbibliotek bör vi arbeta i följande ordning:

  1. Egenskaper och metoder (instans och statisk)
  2. Operatörer
  3. Indexerare (instans och statisk, kan utföras opportunistiskt vid en tidigare tidpunkt)
  4. Annat

Hur mycket vill vi läsa in designen för andra typer av medlemmar i förväg?

extension_member_declaration // add
    : constant_declaration
    | field_declaration
    | method_declaration
    | property_declaration
    | event_declaration
    | indexer_declaration
    | operator_declaration
    | constructor_declaration
    | finalizer_declaration
    | static_constructor_declaration
    | type_declaration
    ;

Kapslade typer

Här är några kommentarer från tidigare diskussioner om vi väljer att gå vidare med kapslade typer av förlängningar.

  • Det skulle uppstå en konflikt om två tilläggsdeklarationer deklarerade olika kapslade tilläggstyper med samma namn och aritet. Vi har ingen lösning för att representera detta i metadata.
  • Den grova metoden som vi diskuterade för metadata:
    1. vi skulle generera en kapslad skeletttyp med ursprungliga typparametrar och inga medlemmar
    2. vi genererar en implementerings kapslad typ med parametrar av typen prepended från tilläggsdeklarationen och alla medlemsimplementeringar som de visas i källan (moduloreferenser till typparametrar)

Konstruktörer

Konstruktorer beskrivs vanligtvis som en instansmedlem i C#, eftersom deras brödtext har åtkomst till det nyligen skapade värdet via nyckelordet this . Detta fungerar dock inte bra för den parameterbaserade metoden för instanstilläggsmedlemmar, eftersom det inte finns något tidigare värde att skicka in som en parameter.

I stället fungerar tilläggskonstruktorer mer som statiska fabriksmetoder. De anses vara statiska medlemmar i den meningen att de inte är beroende av ett mottagarparameternamn. Deras kroppar måste uttryckligen skapa och returnera byggresultatet. Själva medlemmen deklareras fortfarande med konstruktorsyntax, men kan inte ha this eller base initierare och förlitar sig inte på att mottagartypen har tillgängliga konstruktorer.

Det innebär också att tilläggskonstruktorer kan deklareras för typer som inte har egna konstruktorer, till exempel gränssnitt och uppräkningstyper:

public static class Enumerable
{
    extension(IEnumerable<int>)
    {
        public static IEnumerable(int start, int count) => Range(start, count);
    }
    public static IEnumerable<int> Range(int start, int count) { ... } 
}

Tillåter:

var range = new IEnumerable<int>(1, 100);

Kortare formulär

Den föreslagna designen undviker upprepning per medlem av mottagarspecifikationer, men medför att tilläggsmedlemmar kapslas in två nivåer i en statisk klass och tilläggsdeklaration. Det är sannolikt vanligt att statiska klasser endast innehåller en tilläggsdeklaration eller att tilläggsdeklarationer endast innehåller en medlem, och det verkar troligt att vi tillåter syntaktisk förkortning av dessa fall.

Sammanfoga statiska klass- och tilläggsdeklarationer:

public static class EmptyExtensions : extension(IEnumerable source)
{
    public bool IsEmpty => !source.GetEnumerator().MoveNext();
}

Det här ser mer ut som som vi har kallat en "typbaserad" metod, där själva containern för tilläggsmedlemmar är namngiven.

Sammanfoga tilläggsdeklaration och tilläggsmedlem:

public static class Bits
{
    extension(ref ulong bits) public bool this[int index]
    {
        get => (bits & Mask(index)) != 0;
        set => bits = value ? bits | Mask(index) : bits & ~Mask(index);
    }
    static ulong Mask(int index) => 1ul << index;
}
 
public static class Enumerable
{
    extension<TSource>(IEnumerable<TSource> source) public IEnumerable<TSource> Where(Func<TSource, bool> predicate) { ... }
}

Det här ser mer ut som vad vi har kallat en "medlemsbaserad" metod, där varje tilläggsmedlem innehåller sin egen mottagarspecifikation.