Bővítménytagok

Megjegyzés:

Ez a cikk egy funkcióspecifikáció. A specifikáció a funkció tervezési dokumentumaként szolgál. Tartalmazza a specifikáció javasolt módosításait, valamint a funkció tervezése és fejlesztése során szükséges információkat. Ezeket a cikkeket mindaddig közzéteszik, amíg a javasolt specifikációmódosításokat nem véglegesítik, és be nem építik a jelenlegi ECMA-specifikációba.

A szolgáltatás specifikációja és a befejezett implementáció között eltérések lehetnek. Ezeket a különbségeket a vonatkozó nyelvi tervezési értekezlet (LDM) megjegyzései rögzítik.

A funkcióspektusok C# nyelvi szabványba való bevezetésének folyamatáról a specifikációkcímű cikkben olvashat bővebben.

A bajnokkal kapcsolatos kérdés: https://github.com/dotnet/csharplang/issues/8697

Nyilatkozat

Szemantika

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?
    ;

A meghosszabbítási deklarációk csak nem általános, nem beágyazott statikus osztályokban deklarálhatók.
Hiba történt a névvel elnevezett extensiontípusnál.

Hatókörkezelési szabályok

A bővítménydeklaráció típusparaméterei és fogadóparaméterei a bővítménydeklaráció törzsében találhatók. Statikus tagon belül hibát jelent a vevőparaméterre hivatkozni, kivéve egy nameof kifejezésben. Hiba, hogy a tagok a típusparamétereket vagy paramétereket (valamint a helyi változókat és helyi függvényeket közvetlenül a tag törzsében) deklarálják ugyanazzal a névvel, mint a bővítménydeklaráció típusparamétere vagy fogadóparamétere.

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

Nem számít hibának, hogy a tagok neve megegyezik a közrezáró kiterjesztési deklaráció típusparaméterével vagy fogadóparaméterével. A tagnevek nem találhatók közvetlenül egy egyszerű névkeresésben a bővítménydeklarációban; A keresés így a tag helyett az adott név típusparaméterét vagy fogadóparaméterét fogja megtalálni.

A tagok azt eredményezik, hogy a statikus módszereket közvetlenül a belefoglaló statikus osztályba deklarálják, és ezek egyszerű névkereséssel találhatók meg; azonban először egy azonos nevű bővítménydeklarációs típusparaméter vagy fogadóparaméter található.

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

Statikus osztályok bővítménytárolóként

A bővítmények a legfelső szintű, nem általános statikus osztályokban vannak deklarálva, ugyanúgy, mint a bővítménymetelyek, és így együtt létezhetnek a klasszikus kiterjesztési módszerekkel és a nem kiterjesztésű statikus tagokkal:

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

Bővítménydeklarációk

A bővítménydeklaráció névtelen, és minden társított típusparamétert és korlátozást tartalmazó fogadó-specifikációt , majd a bővítménytag-deklarációk készletét tartalmazza. A vevő specifikációja lehet paraméter, vagy - ha csak statikus bővítménytagok deklarálva vannak - típus:

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

A vevő specifikációjában szereplő típust fogadótípusnak nevezzük, és ha van ilyen, a paraméter nevét fogadóparaméternek nevezzük.

Ha a vevőparaméter neve el van nevezve, előfordulhat, hogy a fogadó típusa nem statikus.
A fogadóparaméter nem rendelkezhet módosítókkal, ha nincs megadva, és csak az alábbi és scoped egyéb listában szereplő refness módosítókat használhatja.
A fogadó paraméterre ugyanazok a korlátozások vonatkoznak, mint a klasszikus bővítménymetódus első paraméterére.
Az [EnumeratorCancellation] attribútum figyelmen kívül lesz hagyva, ha a fogadó paraméterre kerül.

Bővítménytagok

A bővítménytag-deklarációk szintaktikailag megegyeznek az osztály- és szerkezetdeklarációk megfelelő példányaival és statikus tagjaival (a konstruktorok kivételével). A példánytagok a fogadó objektumra a fogadó paraméter nevével hivatkoznak.

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

Hiba egy példánybővítmény tagjának megadásakor, ha a bővítmény deklarációja nem ad meg fogadóparamétert:

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

Hiba a következő módosítók megadása egy bővítménydeklaráció egyik tagján: abstract, , virtual, overridenew, sealed, partialés (és protected a kapcsolódó akadálymentességi módosítók).
Hiba a módosító megadása egy readonly bővítménydeklaráció egy tagján.
A bővítménydeklarációk tulajdonságai nem feltétlenül rendelkeznek init tartozékokkal.
A példánytagok tiltva vannak, ha a fogadó paraméter nincs megnevezve.

Minden tagnak olyan neve van, amely eltér a statikus befoglaló osztály nevétől és a kiterjesztett típus nevétől, ha van ilyen.

Hiba egy bővítménytagot az attribútummal [ModuleInitializer] díszíteni.

Refness

Alapértelmezés szerint a fogadó érték szerint továbbítja a fogadót a példánybővítmény tagjainak, ugyanúgy, mint más paraméterek. Azonban a paraméterként megadott bővítménydeklaráció fogadója képes megadni ref, ref readonly és in mindaddig, amíg a fogadó típusa ismert, mint értéktípus.

Nullkezelhetőség és attribútumok

A fogadótípusok lehetnek vagy tartalmazhatnak null értékű referenciatípusokat, és a paraméterek formájában megadott fogadó-specifikációk attribútumokat adhatnak meg:

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

Kompatibilitás a klasszikus bővítménymetelyekkel

A példánykiterjesztési metódusok olyan összetevőket hoznak létre, amelyek megfelelnek a klasszikus bővítménymetelyek által előállítottaknak.

Pontosabban a létrehozott statikus metódus tartalmazza a deklarált bővítménymetódus attribútumait, módosítóit és nevét, valamint a típusparaméter-listát, a paraméterlistát és a kényszerlistát, amelyet a bővítménydeklarációból és a metódusdeklarációból fűz össze ebben a sorrendben:

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

Generál:

[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)  { ... }
}

Működtetők

Bár a bővítmény-operátorok explicit operandustípusokkal rendelkeznek, mégis deklarálni kell őket egy bővítménydeklarációban:

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

Ez lehetővé teszi a típusparaméterek deklarálását és következtetését, és hasonló ahhoz, ahogyan a felhasználó által definiált operátorokat az egyik operandustípuson belül deklarálni kell.

Ellenőrzés

Következtetés: Minden nem metódusalapú bővítménytag esetében a bővítményblokk összes típusparaméterét a bővítmény és a tag paraméterkészletében kell használni.

Egyediség: Egy adott zárható statikus osztályon belül az azonos fogadótípusú bővítménytag-deklarációk (modulo identitásátalakítás és típusparaméternév helyettesítése) egyetlen deklarációs térként lesznek kezelve, hasonlóan az osztály vagy a szerkezet deklarációjának tagjaihoz, és ugyanazokra a szabályokra vonatkoznak az egyediségre vonatkozóan.

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

Ennek az egyediségi szabálynak az alkalmazása magában foglalja az ugyanazon statikus osztályban található klasszikus kiterjesztési módszereket. A bővítménydeklarációk metódusaival való összehasonlítás céljából a this paraméter fogadó-specifikációként, az adott fogadótípusban említett típusparaméterekkel együtt, a többi típusparamétert és metódusparamétert pedig a metódus-aláíráshoz használja:

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

Fogyasztás

A bővítménytagok keresésének megkísérlésekor az importált statikus osztályokban usinglévő összes bővítménydeklaráció jelöltként járul hozzá a tagokhoz, a fogadó típusától függetlenül. Csak a megoldás részeként vannak elvetve a nem kompatibilis fogadótípusokkal rendelkező jelöltek.
Az argumentumok típusa (beleértve a tényleges fogadót is) és a típusparaméterek (kombinálva a bővítmény deklarációjában és a bővítménytag deklarációjában szereplőkkel) között teljes általános típusinferenciát kísérelünk meg.
Explicit típusargumentumok megadásakor a bővítmény deklarációjának és a bővítménytag-deklaráció típusparamétereinek helyettesítésére szolgálnak.

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

A klasszikus bővítménymetelyekhez hasonlóan a kibocsátott implementációs metódusok statikusan is meghívhatók.
Ez lehetővé teszi a fordító számára az azonos nevű és aritású bővítménytagok egyértelműsítését.

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

A statikus bővítménymetódusokat a példánykiterjesztési metódusokhoz hasonlóan feloldjuk (a fogadó típusának további argumentumát fogjuk figyelembe venni).
A bővítménytulajdonságok a bővítménymetódushoz hasonlóan egy paraméterrel (a fogadó paraméterrel) és egyetlen argumentummal (a tényleges fogadóértékkel) lesznek feloldva.

using static Irányelvek

Egy using_static_directive elérhetővé teszi a típusdeklaráció bővítményblokkjainak tagjait a bővítményhozzáféréshez.

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

A korábbiakhoz hasonlóan az adott típus deklarációjában közvetlenül szereplő akadálymentes statikus tagokra (a kiterjesztési módszerek kivételével) közvetlenül lehet hivatkozni.
Ez azt jelenti, hogy a megvalósítási módszerek (a bővítménymetelyek kivételével) közvetlenül statikus metódusként használhatók:

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

A using_static_directive még mindig nem importálja közvetlenül statikus metódusként a bővítménymetódusokat, így a nem statikus bővítménymetódusok implementálási metódusa nem hívható meg közvetlenül statikus metódusként.

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

OverloadResolutionPriorityAttribute

A melléktagok egy zárható statikus osztályon belül az ORPA-értékek alapján rangsorolásnak vannak kitéve. A beágyazó statikus osztályt az ORPA-szabályok által figyelembe vett "tartalmazó típusnak" tekintjük.
A bővítménytulajdonságokon található ORPA-attribútumok át lesznek másolva a tulajdonság tartozékainak implementálási módszereire, így a rendszer tiszteletben tartja a rangsorolást, amikor a bővítményeket egyértelműsítési szintaxissal használják.

Belépési pontok

A bővítményblokkok módszerei nem minősülnek belépési pontnak (lásd: "7.1 Alkalmazás indítása"). Megjegyzés: a megvalósítási módszer továbbra is lehetséges jelölt lehet.

Csökkentés

A bővítménydeklarációk csökkentő stratégiája nem nyelvi szintű döntés. A nyelvi szemantikán túl azonban bizonyos követelményeknek is meg kell felelnie:

  • A létrehozott típusok, tagok és metaadatok formátumát minden esetben egyértelműen meg kell adni, hogy más fordítók is felhasználhassák és létrehozhassák azt.
  • A létrehozott összetevőknek stabilnak kell lenniük, abban az értelemben, hogy az ésszerű későbbi módosítások nem akadályozhatják meg a korábbi verziókhoz összeállított felhasználókat.

Ezeknek a követelményeknek a megvalósítás előrehaladása során további finomítást kell igényelniük, és előfordulhat, hogy szélsőséges esetekben kompromisszumot kell kötni az ésszerű megvalósítási megközelítés érdekében.

Deklarációk metaadatai

Célok

Az alábbi kialakítás a következőket teszi lehetővé:

  • a bővítménydeklaráció szimbólumainak lekerekítése metaadatokon keresztül (teljes és referenciaszerelvények),
  • a bővítménytagokra mutató stabil hivatkozások (xml-dokumentumok),
  • a kibocsátott nevek helyi meghatározása (az EnC esetében hasznos),
  • nyilvános API-nyomkövetés.

Xml-dokumentumok esetén a bővítménytag docID azonosítója a metaadatok bővítménytagjának docID azonosítója. A cref="Extension.extension(object).M(int)" használt docID például az M:Extension.<>E__ExtensionGroupingTypeNameForObject.M(System.Int32), és ez a docID stabil marad a bővítményblokkok újrafordítása és újrarendezése során. Ideális esetben az is stabil marad, ha a bővítményblokkok korlátozásai megváltoznak, de nem találtunk olyan tervet, amely ezt a tagütközések nyelvi kialakítására gyakorolt káros hatás nélkül érné el.

Az EnC esetében hasznos helyben tudni (csak egy módosított kiterjesztés tagot nézve), hogy a frissített kiterjesztés tag hol kerül kibocsátásra a metaadatokban.

A nyilvános API-nyomkövetéshez a stabilabb nevek csökkentik a zajt. Az ilyen helyzetekben azonban a bővítménycsoportozási típusneveknek gyakorlatilag nem szabad szerepet játszaniuk. Ha a bővítmény tagját Mnézzük, nem számít, hogy mi a bővítménycsoportozási típus neve, ami számít, az annak a bővítményblokknak az aláírása, amelyhez tartozik. A nyilvános API-aláírást nem szabad úgy tekinteni, mint Extension.<>E__ExtensionGroupingTypeNameForObject.M(System.Int32) inkább .Extension.extension(object).M(int) Más szóval a bővítménytagoknak két típusparaméter-készlettel és két paraméterkészlettel kell rendelkeznie.

Áttekintés

A bővítményblokkok a CLR-szintű aláírásuk szerint vannak csoportosítva. A rendszer minden CLR-egyenértékűségi csoportot tartalomalapú névvel rendelkező bővítménycsoport-típusként bocsát ki. A CLR-ekvivalenciacsoporton belüli bővítményblokkokat ezután a C# egyenértékűség alcsoportba sorolja. Minden C# egyenértékűségi csoport egy tartalomalapú névvel rendelkező bővítményjelölőtípusként lesz kibocsátva, a megfelelő bővítménycsoportozási típusba ágyazva. A bővítményjelölők típusa egyetlen bővítményjelölőmetódust tartalmaz, amely egy bővítményparamétert kódol. A bővítményjelölő módszer a hozzá tartozó típusával teljes hűséggel kódolja a bővítményblokk aláírását. Az egyes bővítménytagok deklarálása a megfelelő bővítménycsoportozási típusban történik, egy attribútumon keresztül hivatkozik vissza egy bővítményjelölő típusára, és egy legfelső szintű statikus megvalósítási módszer kíséri, módosított aláírással.

A metaadatok kódolásának sémaalapú áttekintése:

[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 ...
}

A beágyazó statikus osztály egy [Extension] attribútummal lesz kibocsátva.

CLR-szintű aláírás és C#-szintű aláírás

A bővítményblokk CLR-szintű aláírása a következőből származik:

  • a típusparaméterek neveinek normalizálása T0, T1stb.
  • attribútumok eltávolítása
  • a paraméternév törlése
  • paramétermódosítók törlése (például ref: , in, scoped...)
  • tuple nevek törlése
  • érvénytelenségi széljegyzetek törlése
  • notnull korlátozások törlése

Megjegyzés: a rendszer megőrzi az egyéb kényszereket, például new(), struct, class, allows ref struct, unmanaged és a típuskényszereket.

Bővítménycsoportozási típusok

A rendszer egy bővítménycsoportozási típust bocsát ki a metaadatok számára a forrásban található, azonos CLR-szintű aláírással rendelkező bővítményblokk-csoportok mindegyikéhez.

  • Neve kimondhatatlan, és a CLR-szintű aláírás tartalma alapján határozható meg. További részletek alább.
  • A típusparaméterek normalizált névvel (T0, , ...) rendelkeznek, T1és nem rendelkeznek attribútumokkal.
  • Nyilvános és lezárt.
  • A specialname jelzővel és egy [Extension] attribútummal van megjelölve.

A bővítménycsoportozási típus tartalomalapú neve a CLR-szintű aláíráson alapul, és a következőket tartalmazza:

  • A bővítményparaméter típusának teljes clR-neve.
    • A hivatkozott típusparaméterek neve normalizálva lesz a következőre T0: , T1stb. a típusdeklarációban megjelenő sorrend alapján.
    • A teljes név nem tartalmazza az azt tartalmazó szerelvényt. Gyakran előfordul, hogy a típusokat a szerelvények között kell áthelyezni, és ez nem szakíthatja meg az XML dokumentum hivatkozásait.
  • A típusparaméterek korlátozásai bele lesznek foglalva és rendezve lesznek, így az újrarendezésük a forráskódban nem változtatja meg a nevet. Kifejezetten:
    • A típusparaméter-megkötések deklarációs sorrendben jelennek meg. Az Nth+1 típusparaméter előtt az Nth+1 típusú paraméterre vonatkozó korlátozások lépnek fel.
    • A típuskorlátozások rendezése a teljes nevek ordinális összehasonlításával lesz.
    • A nem típusmegkötések determinisztikus sorrendben vannak rendezve, és úgy vannak kezelve, hogy elkerülhető legyen a kétértelműség vagy a típuskorlátozásokkal való ütközés.
  • Mivel ez nem tartalmaz attribútumokat, szándékosan figyelmen kívül hagyja a C#-specifikus elemeket, mint például a tuple nevek, a nullabilitás stb.

Megjegyzés: A név garantáltan stabil marad az újrafordítások, az újrarendezések és a C# jellemzők olyan módosításai esetén, amelyek nem befolyásolják a CLR-szintű aláírást.

Bővítményjelölők típusai

A jelölőtípus újra deklarálja az azt tartalmazó csoportosítási típus (bővítménycsoportozási típus) típusparamétereit, hogy a bővítményblokkok C#-nézetének teljes megbízhatóságát megkapja.

A rendszer egy kiterjesztésjelölőtípust bocsát ki a metaadatok számára a forrásban lévő, azonos C#-szintű aláírással rendelkező bővítményblokkok minden egyes halmazához.

  • Neve kimondhatatlan, és a bővítményblokk C#-szintű aláírásának tartalma alapján határozható meg. További részletek alább.
  • A forrásban deklarált típusú csoportosítási típus típusparamétereit (beleértve a nevet és az attribútumokat) ismételten bejelenti.
  • Nyilvános és statikus.
  • A jelölővel specialname van megjelölve.

A bővítményjelölő típusának tartalomalapú neve az alábbiakon alapul:

  • A típusparaméterek neve szerepel a bővítménydeklarációban megjelenő sorrendben
  • A típusparaméterek attribútumai úgy lesznek belefoglalva és rendezve, hogy a forráskódban való átrendezés nem változtatja meg a nevet.
  • A típusparaméterek korlátozásai bele lesznek foglalva és rendezve lesznek, így az újrarendezésük a forráskódban nem változtatja meg a nevet.
  • A kiterjesztett típus teljes C#-neve
    • Ez olyan elemeket fog tartalmazni, mint a nullálható annotációk, a tupel nevek stb.
    • A teljes név nem tartalmazza az azt tartalmazó szerelvényt
  • A bővítményparaméter neve
  • A bővítményparaméter (ref, , ref readonly, scoped...) módosítói determinisztikus sorrendben
  • A bővítményparaméterre alkalmazott attribútumok teljes név- és attribútumargumentumai determinisztikus sorrendben

Megjegyzés: A név garantáltan stabil marad az újrafordítás és az újrarendezés során.
Megjegyzés: A bővítményjelölő típusok és a bővítményjelölő metódusok a referenciaszerelvények részeként kerülnek kibocsátásra.

Bővítményjelző módszer

A jelölőmetódus célja a bővítményblokk bővítményparaméterének kódolása. Mivel tagja a bővítményjelölő típusának, hivatkozhat a bővítményjelölő típusának újra deklarált típusparaméterére.

Minden kiterjesztésjelölőtípus egyetlen metódust, a bővítményjelölő metódust tartalmazza.

  • Ez statikus, nem általános, void visszatérésű, és az úgynevezett <Extension>$.
  • Egyetlen paramétere a bővítményparaméter attribútumaival, refness-ével, típusával és nevével rendelkezik.
    Ha a bővítményparaméter nem ad meg nevet, akkor a paraméter neve üres.
  • A jelölővel specialname van megjelölve.

A jelölőmetódus akadálymentessége a legkevésbé korlátozó akadálymentesség lesz a megfelelő deklarált bővítménytagok között; amennyiben nincs ilyen deklarálva, private értelmében lesz alkalmazva.

Bővítménytagok

A forrásban lévő bővítményblokk metódus-/tulajdonságdeklarációi a metaadatok bővítménycsoportozási típusának tagjaiként jelennek meg.

  • Az eredeti módszerek szignatúrái megmaradnak (az attribútumokat is beleértve), de a testüket lecserélik throw NotImplementedException().
  • Ezekre az il-ben nem szabad hivatkozni.
  • A metódusok, tulajdonságok és tartozékaik a tag bővítményblokkjának megfelelő kiterjesztésjelölőtípus nevére hivatkozva vannak megjelölve [ExtensionMarkerName("...")] .

Implementálási módszerek

A forrásban lévő bővítményblokk metódus-/tulajdonságdeklarációinak metódustestei statikus implementációs metódusként jelennek meg a legfelső szintű statikus osztályban.

  • A megvalósítási metódus neve megegyezik az eredeti metódus nevével.
  • A bővítményblokkból származó típusparaméterek az eredeti metódus típusparamétereihez vannak előállítva (az attribútumokat is beleértve).
  • Ugyanazokkal az akadálymentességgel és attribútumokkal rendelkezik, mint az eredeti módszer.
  • Ha statikus metódust implementál, ugyanazokkal a paraméterekkel és visszatérési típussal rendelkezik.
  • Ha implementál egy példánymetódust, egy előre felfűzött paraméterrel rendelkezik az eredeti metódus aláírásához. Ennek a paraméternek az attribútumai, a refness, a típus és a név a megfelelő bővítményblokkban deklarált bővítményparaméterből származik.
  • Az implementálási metódusok paraméterei a implementálási módszer által birtokolt típusparaméterekre vonatkoznak a bővítményblokkok helyett.
  • Ha az eredeti metódus egy példány metódus, akkor a megvalósítási metódus [Extension] attribútummal van megjelölve.

ExtensionMarkerName attribútum

A ExtensionMarkerNameAttribute típus csak fordítóhoz használható – a forrásban nem engedélyezett. A típusdeklarációt a fordító szintetizálja, ha még nem szerepel a fordításban.

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

Megjegyzés: Bár néhány attribútumcél szerepel a jövőbiztosításhoz (bővítmények beágyazott típusai, bővítménymezők, bővítményesemények), az AttributeTargets.Konstruktor nem szerepel benne, mivel a bővítménykonstruktorok nem konstruktorok.

példa

Megjegyzés: egyszerűsített tartalomalapú neveket használunk a példához az olvashatóság érdekében. Megjegyzés: mivel a C# nem képes a típusparaméter újradeklarálására, a metaadatokat képviselő kód érvénytelen C# kód.

Íme egy példa, amely bemutatja, hogyan működik a csoportosítás tagok nélkül:

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

kibocsátva:

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

Íme egy példa a tagok kibocsátásának módjára:

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() { ... }
}

kibocsátva:

[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() { ... }
}

Amikor bővítménytagokat használnak a forrásban, ezeket a végrehajtási módszerekre hivatkozva bocsátjuk ki. Például: egy enumerableOfInt.Method() hívása statikus hívásként lenne kibocsátva IEnumerableExtensions.Method<int>(enumerableOfInt).

XML-dokumentumok

A bővítményblokkra vonatkozó doc-megjegyzések a jelölőtípushoz lesznek kibocsátva (a bővítményblokk DocID azonosítója az alábbi példában található E.<>E__MarkerContentName_For_ExtensionOfT'1 ).
Hivatkozhatnak a bővítményparaméterre és a típusparaméterekre a <paramref> és <typeparamref> használatával.
Megjegyzés: Nem dokumentálhatja a bővítményparamétert vagy a típusparamétereket (<param> és <typeparam> használatával) egy bővítménytagon.

Ha két bővítményblokkot bocsát ki egy jelölőtípusként, a dokumentum megjegyzései is egyesülnek.

Az xml-dokumentumokat kezelő eszközök feladata, hogy a <param> és <typeparam> elemeket a kiterjesztési blokkból a megfelelő kiterjesztési tagokra másolják, azaz a paraméterinformációkat csak a példánytagokra kell másolni.

Kibocsátanak egy <inheritdoc> a végrehajtási módszerekre vonatkozóan, és ez cref segítségével hivatkozik a megfelelő bővítménytagra. A getter implementálási módszere például a bővítménytulajdonság dokumentációjára hivatkozik. Ha a bővítménytag nem rendelkezik dokumentum megjegyzésekkel, akkor a függvény nem adja meg a <inheritdoc> megjegyzést.

A bővítményblokkok és a bővítménytagok esetében jelenleg nem figyelmeztetjük, ha:

  • a bővítményparaméter dokumentálva van, de a bővítménytag paraméterei nem
  • vagy fordítva
  • vagy a nem dokumentált típusparaméterekkel rendelkező egyenértékű forgatókönyvekben

Például a következő dokumentum megjegyzései:

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

a következő xml-t adja meg:

<?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-hivatkozások

A bővítményblokkokat úgy kezelhetjük, mint a beágyazott típusokat, amelyek az aláírásukkal kezelhetők (mintha egyetlen bővítményparaméterrel rendelkező metódus lenne). Példa: E.extension(ref int).M().

A cref azonban nem tudja kezelni magát a bővítményblokkot. E.extension(int) egy "extension" nevű metódusra hivatkozhat a típusban 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
  }
}

A keresés úgy van beállítva, hogy az összes megfelelő kiterjesztési blokkban keres.
Mivel nem engedélyezzük a bővítménytagokra való nem minősített hivatkozásokat, a cref ezeket is letiltaná.

A szintaxis a következő:

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
  ;

Hiba a extension_member_cref használata a legfelső szinten (extension(int).M) vagy egy másik bővítménybe (E.extension(int).extension(string).M) ágyazva.

Kritikus változások

Típusok és aliasok nem nevezhetők "extension"-nek.

Nyitott problémák

A dokumentum nyitott problémákkal kapcsolatos ideiglenes szakasza, beleértve a nem definiált szintaxis és az alternatív tervek megvitatását
  • Módosítani kell a fogadó összetevő követelményeit egy bővítmény elem elérésekor? (megjegyzés)
  • Erősítse meg extension vs. extensions kulcsszóként (válasz: extension, LDM 2025-03-24)
  • Győződjön meg arról, hogy le szeretnénk tiltani [ModuleInitializer] (válasz: igen, letiltás, LDM 2025-06-11)
  • Győződjön meg arról, hogy elvethetjük-e a bővítményblokkokat mint belépési pont jelölteket (válasz: igen, elvetés, LDM 2025-06-11)
  • Erősítse meg a LangVer-logikát (hagyja ki az új bővítményeket, és vegye figyelembe és jelentse őket, ha kiválasztják) (válasz: feltétel nélküli kötés és LangVer-hiba jelentése a példánykiterjesztési módszerek kivételével, LDM 2025-06-11)
  • Kötelező legyen a partial az olyan bővítményblokkokhoz, amelyek egyesülnek, és össze kell vonni a dokumentummegjegyzéseiket? (válasz: a dokumentum megjegyzései csendesen egyesülnek, amikor a blokkok egyesülnek, nincs partial szükség, 2025-09-03 e-mailben megerősítve)
  • Győződjön meg arról, hogy a tagokat nincs szükség az őket magában foglaló vagy kiterjesztett típusok szerint elnevezni. (válasz: igen, megerősítve e-mailben 2025-09-03)

A csoportosítási/ütközési szabályok újbóli kezelése a hordozhatósági probléma fényében: https://github.com/dotnet/roslyn/issues/79043

(válasz: ez a forgatókönyv a tartalomalapú típusnevekkel rendelkező új metaadat-tervezés részeként lett megoldva, engedélyezett)

A jelenlegi logika az azonos fogadótípusú bővítményblokkok csoportosítása. Ez nem veszi figyelembe a korlátozásokat. Ez hordozhatósági problémát okoz ezzel a forgatókönyvvel:

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

A javaslat ugyanazt a csoportosítási logikát használja, amelyet a bővítménycsoportozás típusának tervezéséhez tervezünk, nevezetesen a CLR-szintű megkötések figyelembe vételéhez (azaz figyelmen kívül hagyva a notnullt, a rekordneveket, a nullitási megjegyzéseket).

Kódolni kell a "refness"-t a csoportosítási típus nevében?

  • Tekintse át azt a javaslatot, amely ref nem szerepel a bővítménycsoport-típusnévben (a WG újra megvizsgálja a csoportosítási/ütközési szabályokat, LDM 2025-06-23) (válasz: e-mailben megerősítve: 2025-09-03)
public static class E
{
  extension(ref int)
  {
    public static void M()
  }
}

A rendszer a következő módon bocsátja ki:

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

A harmadik fél által készített CREF-referenciák E.extension(ref int).M úgy lesznek kibocsátva, mint M:E.<>ExtensionGroupingTypeXYZ.M(), ha ref-t eltávolítanak vagy hozzáadnak egy bővítményparaméterhez, valószínűleg nem szeretnénk, hogy a CREF megszakadjon.

Nem nagyon foglalkozunk ezzel a forgatókönyvvel, mivel a bővítményként való használat kétértelmű lenne:

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

Ez a forgatókönyv azonban fontos (a hordozhatóság és a hasznosság szempontjából), és az ütközési szabályok módosítása után ez a javasolt metaadat-kialakításnak is működnie kell:

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

A refness figyelembevételének elhagyása hátrányos, mert elveszítjük a hordozhatóságot ebben a forgatókönyvben.

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

név szerinti

  • Tiltani kéne a kiterjesztéstulajdonságokat a nameof-ban, mint ahogy tesszük a klasszikus és az új bővítménymetódusokkal? (válasz: a "nameof(EnclosingStaticClass.ExtensionMember) nevet szeretnénk használni. Tervezésre van szükség, valószínűleg a .NET 10-ből. LDM 2025-06-11)

mintaalapú szerkezetek

Módszerek

  • Hol érdemes új bővítménymetelyeket bevetni? (válasz: ugyanazok a helyek, ahol a klasszikus bővítménymetszetek játszhatók le, LDM 2025-05-05)

Ez a következőket foglalja magában:

  • GetEnumerator / GetAsyncEnumerator ban foreach
  • Deconstruct a dekonstruálásban, a pozíciómintában és a foreach ciklusban
  • Add a gyűjtemény inicializálóiban
  • GetPinnableReference a(z) fixed rendszerben
  • GetAwaiter a(z) await rendszerben

Ez kizárja a következőt:

  • Dispose / DisposeAsync using és foreach
  • MoveNext / MoveNextAsync ban foreach
  • Slice és int indexelők implicit indexelőkben (és esetleg listamintákban?)
  • GetResult a(z) await rendszerben

Tulajdonságok és indexelők

  • Hol érdemes a bővítménytulajdonságokat és indexelőket használni? (válasz: kezdjük a néggyel, LDM 2025-05-05)

A következőket tartalmaznánk:

  • objektum-inicializáló: new C() { ExtensionProperty = ... }
  • szótár inicializáló: new C() { [0] = ... }
  • with: x with { ExtensionProperty = ... }
  • tulajdonságminták: x is { ExtensionProperty: ... }

Kizárjuk a következőt:

  • Current a(z) foreach rendszerben
  • IsCompleted a(z) await rendszerben
  • Count / Length tulajdonságok és indexelők a listamintában
  • Count / Length tulajdonságok és indexelők implicit indexelőkben
Visszatérő delegáltakat adó tulajdonságok
  • Győződjön meg arról, hogy az alakzat bővítménytulajdonságai csak a LINQ-lekérdezésekben kerülnek alkalmazásra, hogy megfeleljenek a példánytulajdonságoknak. (válasz: van értelme, LDM 2025-04-06)
Lista- és terjedési minta
  • Győződjön meg arról, hogy a bővítményindexelőknek Index/Range listamintákban kell játszaniuk (válasz: a C# 14 esetében nem releváns)
Tekintsük át, hogy a bővítménytulajdonságok hogyan kerülnek szerepbe Count/Length

Gyűjteménykifejezések

  • A bővítmény Add működik
  • A bővítmény GetEnumerator terjesztéshez működik
  • A bővítmény GetEnumerator nem befolyásolja az elemtípus meghatározását (példánynak kell lennie)
  • A statikus Create bővítménymetódusok nem számítanak áldott létrehozási módszernek
  • Hatással vannak-e a bővítmények megszámlálható tulajdonságai a gyűjteménykifejezésekre?

params gyűjtemények

  • A kiegészítők Add nem befolyásolják, hogy milyen típusok engedélyezettek params

szótárkifejezések

  • Győződjön meg arról, hogy a bővítményindexelők nem játszanak a szótárkifejezésekben, mivel az indexelő jelenléte szerves része annak, ami meghatározza a szótártípust. (válasz: c# 14 esetén nem releváns)

extern

Elnevezési/számozási séma bővítménytípushoz

Probléma
A jelenlegi számozási rendszer problémákat okoz a nyilvános API-k ellenőrzésével kapcsolatban, ami biztosítja, hogy a nyilvános API-k egyezzenek a csak referencia-szerelvények és a megvalósítási szerelvények között.

Meg kellene tennünk az alábbi módosítások egyikét? (válasz: tartalomalapú elnevezési sémát vezetünk be a nyilvános API stabilitásának növelése érdekében, és az eszközöket továbbra is frissíteni kell a jelölőmódszerek figyelembe vételéhez)

  1. az eszköz beállítása
  2. tartalomalapú elnevezési séma (TBD) használata
  3. Lehetővé teszi a név szabályozását valamilyen szintaxis használatával

Az új általános kiterjesztésű cast metódus továbbra sem működik a LINQ-ban

Probléma
A szerepkörök/bővítmények korábbi terveiben csak a metódus típusargumentumait lehetett explicit módon megadni.
Most azonban, hogy a klasszikus bővítménymetelyekről való látszólagos átmenetre összpontosítunk, az összes típusargumentumot explicit módon kell megadni.
Ez nem oldja meg a LINQ-ban a bővítmény cast metódusának használatával kapcsolatos problémát.

Módosítsuk a bővítmények funkcióját, hogy alkalmazkodjunk ehhez a forgatókönyvhöz? (válasz: nem, emiatt nem kell újra átgondolnunk a bővítmény kiterjesztés tervezését, LDM 2025-05-05)

A bővítményparaméter korlátozása egy bővítménytagon

Engedélyeznünk kell a következőket? (válasz: nem, ezt később lehet hozzáadni)

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

Null lehetőség

  • Erősítse meg a jelenlegi kialakítást, azaz a maximális hordozhatóságot/kompatibilitást (válasz: igen, 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!;
    }

Metadaták

  • A csontváz metódusoknak NotSupportedException vagy inkább valamilyen más szokásos kivételt kéne dobniuk (jelenleg ezt tesszük throw null;)? (válasz: igen, LDM 2025-04-17)
  • Több paramétert is el kell fogadnunk a jelölőmetódusban a metaadatokban (abban az esetben, ha az új verziók több információt adnak hozzá)? (válasz: továbbra is szigorúak maradhatunk, LDM 2025-04-17)
  • A bővítményjelölőt vagy a beszédes megvalósítási metódusokat speciális névvel kell megjelölni? (válasz: a marker metódust speciális névvel kell megjelölni, és ellenőrizni kell, de implementálási módszereket nem, LDM 2025-04-17)
  • Akkor is hozzá kell adni [Extension] attribútumot a statikus osztályhoz, ha nincs példánykiterjesztési módszer? (válasz: igen, LDM 2025-03-10)
  • Győződjön meg arról, hogy attribútumot kell hozzáadni [Extension] a megvalósítási lekérésekhez és a setterekhez is. (válasz: nem, LDM 2025-03-10)
  • Győződjön meg arról, hogy a bővítménytípusokat speciális névvel kell megjelölni, és a fordítónak szüksége lesz erre a jelölőre a metaadatokban (ez az előzetes verziótól való kompatibilitástörő változás) (válasz: jóváhagyva, LDM 2025-06-23)

statikus gyár forgatókönyve

  • Mik a statikus metódusok ütközési szabályai? (válasz: meglévő C#-szabályok használata az elzárt statikus típushoz, relaxáció nélkül, LDM 2025-03-17)

Keresés

  • Hogyan oldhatóak meg a példánymetódus-meghívások most, hogy kimondható implementációnevek állnak rendelkezésünkre? Előnyben részesítjük a csontvázmetódust a megfelelő megvalósítási módszerrel.
  • Statikus bővítménymetódusok feloldása (válasz: csakúgy, mint a példánykiterjesztési módszerek, LDM 2025-03-03)
  • Hogyan oldható fel a tulajdonságok? (széles vonásokkal megválaszolva LDM 2025-03-03, de a jobbítás érdekében nyomonkövetést igényel)
  • Hatókörkezelési és árnyékolási szabályok bővítményparaméterekhez és típusparaméterekhez (válasz: a bővítményblokk hatókörében, az árnyékolás nem engedélyezett, LDM 2025-03-10)
  • Hogyan alkalmazható az ORPA az új bővítménymetelyekre? (válasz: a bővítményblokkokat transzparensként kezeli, az ORPA "tartalmazó típusa" az LDM 2025-04-17 nevű statikus osztályt foglalja magában)
public static class Extensions
{
    extension(Type1)
    {
        [OverloadResolutionPriority(1)]
        public void Overload(...)
    }
    extension(Type2)
    {
        public void Overload(...)
    }
}
  • Az ORPA alkalmazható az új bővítménytulajdonságokra? (válasz: igen, és az ORPA-t másolni kell a megvalósítási módszereknél, LDM 2025-04-23)
public static class Extensions
{
    extension(int[] i)
    {
        public P { get => }
    }
    extension(ReadOnlySpan<int> r)
    {
       [OverloadResolutionPriority(1)]
       public P { get => }
    }
}
  • Hogyan lehet újból létrehozni a klasszikus bővítményfeloldási szabályokat? Megteszünk?
    1. frissítse a klasszikus bővítménymetelyek szabványát, és használja ezt az új bővítménymetelyek leírására is,
    2. A klasszikus bővítménymódszerek meglévő nyelvének megtartása, amely az új bővítménymódszerek leírására is használható, de mindkettő esetében ismert specifikáció eltérés van.
    3. Tartsa meg a klasszikus kiterjesztési módszerek meglévő nyelvét, de az új kiterjesztési módszerekhez használjon más nyelvet, és csak a klasszikus kiterjesztési módszerek esetében legyen ismert eltérés a specifikációtól.
  • Győződjön meg arról, hogy nem szeretnénk explicit típusú argumentumokat letiltani egy tulajdonsághozzáférésen (válasz: nincs tulajdonsághozzáférés explicit típusargumentumokkal, a WG-ben tárgyalt)
string s = "ran";
_ = s.P<object>; // error

static class E
{
    extension<T>(T t)
    {
        public int P => 0;
    }
}
  • Győződjön meg arról, hogy jobbsági szabályokat szeretnénk alkalmazni akkor is, ha a fogadó típus (válasz: a statikus bővítménytagok feloldásakor figyelembe kell venni egy csak típusra vonatkozó bővítményparamétert, 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;
    }
}
  • Ellenőrizze, hogy elfogadjuk-e a kétértelműséget, amikor mindkét módszer és tulajdonság alkalmazható (válasz: egy javaslatot kell kidolgoznunk, amely meghaladja a jelenlegi helyzetet, .NET 10-re halasztva, LDM 2025-06-23)
  • Győződjön meg arról, hogy nem törekszünk arra, hogy jobb megoldást találjunk az összes tag esetében, mielőtt meghatároznánk a nyertes tagtípust (válasz: punting out of .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;
    }
}
  • Rendelkezünk implicit fogadóval a bővítménydeklarációkban? (válasz: nem, korábban már szó volt az LDM-ben)
static class E
{
    extension(object o)
    {
        public void M() 
        {
            M2();
        }
        public void M2() { }
    }
}
  • Engedélyeznünk kell a keresést a típusparaméteren? (vitafórum) (válasz: nem, várjuk a visszajelzést, LDM 2025-04-16)

Hozzáférhetőség

  • Mit jelent az akadálymentesség a bővítménydeklarációban? (válasz: a bővítménydeklarációk nem számítanak akadálymentességi hatókörnek, LDM 2025-03-17)
  • Alkalmazzuk az "inkonzisztens hozzáférhetőség" ellenőrzést a vevőparaméterre, még akkor is, ha statikus tagokról van szó? (válasz: igen, 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 { }
}

Bővítménydeklaráció érvényesítése

  • Ellazítsuk a típusparaméter érvényesítését (következtetés: az összes típusparaméternek meg kell jelennie a bővítményparaméter típusában), ahol csak metódusok vannak? (válasz: igen, LDM 2025-04-06) Ezzel 100% klasszikus bővítési módszer portolását teszi lehetővé.
    Ha van TResult M<TResult, TSource>(this TSource source), portként használhatja extension<TResult, TSource>(TSource source) { TResult M() ... }-t.

  • Erősítse meg, hogy a bővítményekben engedélyezni kellene-e az init-only hozzáférőket (válasz: rendben van, egyelőre tiltsák le, LDM 2025-04-17)

  • Engedélyezhető-e extension(int receiver) { public void M2() {} }extension(ref int receiver) { public void M2() {} }az egyetlen különbség a fogadó ref-nessében? (válasz: nem, tartsa meg a specifikált szabályt, LDM 2025-03-24)

  • Panaszkodjunk egy ilyen extension(object receiver) { public int P1 => 1; }extension(object receiver) { public int P1 {set{}} }konfliktus miatt? (válasz: igen, tartsa meg a meghatározott szabályt, LDM 2025-03-24)

  • Panaszkodjunk olyan csontváz metódusok közötti ütközésekről, amelyek nem jelentenek ütközést a megvalósítási módszerek között? (válasz: igen, tartsa meg a meghatározott szabályt, LDM 2025-03-24)

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

Az aktuális ütközési szabályok a következők: 1. ellenőrizze, hogy nincs-e ütközés a hasonló bővítményeken belül osztály-/struktúraszabályok használatával, 2. ellenőrizze, hogy nincsenek-e ütközések a különböző bővítmény-deklarációk implementálási módszerei között.

  • Szükségünk van még a szabályok első részére? (válasz: igen, megtartjuk ezt a struktúrát, mivel segít az API-k fogyasztásában, LDM 2025-03-24)

XML-dokumentumok

  • Támogatott a paramref vevő paraméter a bővítési tagoknál? Még statikusan is? Hogyan van kódolva a kimenetben? Valószínűleg a szabványos módszer <paramref name="..."/> egy ember számára is működik, de fennáll a veszélye annak, hogy egyes meglévő eszközök nem fogják boldogan megtalálni az API paraméterei között. (válasz: igen paramref bővítményparaméter engedélyezve van a bővítménytagokon, LDM 2025-05-05)
  • Át kellene másolnunk a dokumentációs megjegyzéseket a megvalósítási eljárásokba, beszédes nevekkel? (válasz: nincs másolás, LDM 2025-05-05)
  • A <param> vevőparaméternek megfelelő elemet át kell másolni a bővítménytárolóból, ha példánymetódusokról van szó? Van még valami, amit át kell másolni a tárolóból a megvalósítási módszerekre (<typeparam> stb.) ? (válasz: nincs másolás, LDM 2025-05-05)
  • A <param> bővítményparamétert felülbírálásként kell engedélyezni a bővítménytagokon? (válasz: nem, egyelőre LDM 2025-05-05)
  • Megjelenne valahol a bővítményblokkok összegzése?

CREF

  • Szintaxis megerősítése (válasz: a javaslat jó, LDM 2025-06-09)
  • Hivatkozhat-e bővítményblokkra (E.extension(int))? (válasz: nem, LDM 2025-06-09)
  • Lehet-e olyan tagra hivatkozni, aki nem minősített szintaxist használ: extension(int).Member? (válasz: igen, LDM 2025-06-09)
  • Használjunk-e különböző karaktereket a kimondhatatlan névhez, hogy elkerüljük az XML-eket? (válasz: áthárítás a WG-re, LDM 2025-06-09)
  • Győződjön meg arról, hogy a vázra és a megvalósítási módszerekre való hivatkozás lehetséges: E.M vs. E.extension(int).M. Mindkettő szükségesnek tűnik (a kiterjesztési tulajdonságok és a klasszikus kiterjesztési metódusok hordozhatósága). (válasz: igen, LDM 2025-06-09)
  • Problémásak a bővítmény metaadatainak nevei a verziószámozási dokumentumok esetében? (válasz: igen, el fogunk mozdulni a sorszámoktól, és egy tartalomalapú stabil elnevezési sémát fogunk használni)

További tagtípusok támogatásának hozzáadása

Nem kell egyszerre implementálnunk ezt a kialakítást, hanem egy vagy néhány tagtípus szerinti megközelítést alkalmazhatunk. Az alapvető kódtárak ismert forgatókönyvei alapján a következő sorrendben kell dolgoznunk:

  1. Tulajdonságok és metódusok (példány és statikus)
  2. Működtetők
  3. Indexelők (példány és statikus, lehetőség szerint korábbi időpontban opportunista módon végrehajtva)
  4. Van még valami?

Mennyire szeretnénk előre átgondolni más típusú tagok számára a tervezést?

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
    ;

Beágyazott típusok

Ha úgy döntünk, hogy továbblépünk a bővítmények beágyazott típusaival, íme néhány megjegyzés a korábbi megbeszélésekből:

  • Ütközés lenne, ha két bővítménydeklaráció egymásba ágyazott bővítménytípusokat deklarálna ugyanazokkal a névvel és aritással. Nincs megoldásunk a metaadatokban való ábrázoláshoz.
  • Az általunk megvitatott durva megközelítés a metaadatokra vonatkozóan:
    1. egy csontvázba ágyazott típust bocsátanánk ki eredeti típusparaméterekkel és tagok nélkül
    2. egy beágyazott implementációtípust bocsátanánk ki a bővítménydeklarációban előre felírt típusparaméterekkel, valamint a forrásban megjelenő összes tag-implementációval (modulo a típusparaméterekre hivatkozik)

Konstruktorok

A konstruktorokat általában a C# példánytagjaként írják le, mivel a törzsük a kulcsszón keresztül fér hozzá az this újonnan létrehozott értékhez. Ez azonban nem működik jól a példánybővítmény-tagok paraméteralapú megközelítéséhez, mivel paraméterként nincs előzményérték.

Ehelyett a bővítménykonstruktorok a statikus gyári módszerekhez hasonlóan működnek. Statikus tagoknak minősülnek abban az értelemben, hogy nem függnek a fogadó paraméter nevétől. A testüknek explicit módon létre kell hoznia és vissza kell adnia az építési eredményt. Maga a tag továbbra is konstruktorszintaxissal van deklarálva, de nem rendelkezhet this vagy base inicializálókkal, és nem támaszkodik arra a fogadótípusra, amely akadálymentes konstruktorokkal rendelkezik.

Ez azt is jelenti, hogy a bővítménykonstruktorok olyan típusok esetében deklarálhatók, amelyek nem rendelkeznek saját konstruktorokkal, például interfészekkel és enumerálási típusokkal:

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

Lehetővé teszi:

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

Rövidebb űrlapok

A javasolt kialakítás elkerüli a fogadó specifikációinak tagonkénti ismétlődését, de végül a bővítménytagok két mélyen beágyazva lesznek egy statikus osztályba és egy bővítménydeklarációba. Valószínűleg gyakori, hogy a statikus osztályok csak egy kiterjesztési deklarációt tartalmaznak, vagy hogy a bővítménydeklarációk csak egy tagot tartalmaznak, és valószínűnek tűnik számunkra, hogy lehetővé tegyük ezeknek az eseteknek a szintaktikai rövidítését.

Statikus osztály- és bővítménydeklarációk egyesítése:

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

Ez végül jobban hasonlít arra, amit "típusalapú" megközelítésnek hívunk, ahol a bővítménytagok tárolója maga is el van nevezve.

Bővítmény deklarációjának és tagjának egyesítése:

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

Ez végül jobban hasonlít arra, amit "tagalapú" megközelítésnek hívunk, ahol minden bővítménytag saját fogadó specifikációt tartalmaz.