Sdílet prostřednictvím


Členové rozšíření

Poznámka:

Tento článek je specifikace funkce. Specifikace slouží jako návrhový dokument pro funkci. Zahrnuje navrhované změny specifikace spolu s informacemi potřebnými při návrhu a vývoji funkce. Tyto články se publikují, dokud nebudou navrhované změny specifikace finalizovány a začleněny do aktuální specifikace ECMA.

Mezi specifikací funkce a dokončenou implementací může docházet k nějakým nesrovnalostem. Tyto rozdíly jsou zachyceny v příslušných poznámkách ze schůzky jazykového návrhu (LDM).

Další informace o procesu přijetí specifikací funkcí do jazyka C# najdete v článku o specifikacích .

Problém šampiona: https://github.com/dotnet/csharplang/issues/8697

Prohlášení

Syntaxe

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

Deklarace rozšíření se smí deklarovat pouze ve statických třídách, které jsou negenerické a nevnořené.
Jedná se o chybu pro typ, který má být pojmenován extension.

Pravidla oborů

Parametry typu a parametr přijímače v deklaraci rozšíření jsou v rámci těla této deklarace rozšíření. Jedná se o chybu odkazující na parametr přijímače ze statického členu s výjimkou výrazu nameof . Jedná se o chybu, kdy členové deklarují parametry nebo parametry typu (stejně jako místní proměnné a místní funkce přímo v těle člena) se stejným názvem jako parametr typu nebo parametr příjemce deklarace rozšíření.

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

Není chybou, že členové mají stejný název jako typové nebo přijímací parametry v deklaraci rozšíření. Názvy členů nejsou přímo nalezeny v jednoduchém vyhledávání názvů v deklaraci rozšíření; Vyhledávání tedy najde parametr typu nebo parametr příjemce tohoto názvu, nikoli člena.

Členové způsobují statické metody, které jsou deklarovány přímo na obklopující statickou třídu, a ty lze najít prostřednictvím jednoduchého vyhledávání názvů; ale nejprve se najde parametr typu deklarace rozšíření nebo parametr příjemce se stejným názvem.

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

Statické třídy jako kontejnery rozšíření

Rozšíření jsou deklarována uvnitř obecných statických tříd nejvyšší úrovně, stejně jako metody rozšíření dnes, a mohou tak existovat společně s klasickými rozšiřujícími metodami a statickými členy bez rozšíření:

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

Deklarace rozšíření

Deklarace rozšíření je anonymní a poskytuje specifikaci příjemce s libovolnými přidruženými parametry a omezeními typu následovanými sadou deklarací člena rozšíření. Specifikace příjemce může být ve formě parametru nebo , pokud jsou deklarovány pouze statické členy rozšíř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) { ... }
    }
}

Typ ve specifikaci příjemce se označuje jako typ příjemce a název parametru, pokud existuje, se označuje jako parametr příjemce.

Pokud je parametr příjemce pojmenován, typ příjemce nemusí být statický.
Parametr přijímače nesmí mít modifikátory, pokud není pojmenován; v opačném případě jsou povoleny pouze níže uvedené modifikátory refness a scoped.
Parametr přijímače má stejná omezení jako první parametr klasické metody rozšíření.
Atribut [EnumeratorCancellation] je ignorován, pokud je umístěn na parametr příjemce.

Členové rozšíření

Deklarace členů rozšíření jsou syntakticky identické s odpovídajícími instancemi a statickými členy v deklaracích tříd a struktur (s výjimkou konstruktorů). Členové instance odkazují na příjemce s názvem parametru příjemce:

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

Jedná se o chybu při zadání člena rozšíření instance, pokud uzavřená deklarace rozšíření neurčila parametr příjemce:

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

Je chybou specifikovat následující modifikátory u člena deklarace rozšíření: abstract, virtual, override, new, sealed, partial a protected (a související modifikátory přístupnosti).
Jedná se o chybu, která readonly určuje modifikátor člena deklarace rozšíření.
Vlastnosti v deklarací rozšíření nemusí mít init přístupové objekty.
Členové instance jsou zakázáni, pokud je parametr příjemce nepojmenovaný.

Všichni členové mají názvy, které se liší od názvu statické ohraničující třídy a názvu rozšířeného typu, pokud má jeden.

Jedná se o chybu při dekoraci člena rozšíření atributem [ModuleInitializer] .

Refnost

Ve výchozím nastavení je příjemce předán členům rozšíření instance podle hodnoty, stejně jako ostatní parametry. Příjemce deklarace rozšíření ve formuláři parametru však může zadat refref readonly a in, pokud je typ příjemce znám jako typ hodnoty.

Možnosti a atributy s hodnotou null

Typy přijímačů můžou být nebo mohou obsahovat odkazové typy s možnou hodnotou null a specifikace příjemce, které jsou ve formě parametrů, mohou určovat atributy:

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

Kompatibilita s klasickými metodami rozšíření

Metody rozšíření instance generují artefakty, které odpovídají artefaktům vytvořeným klasickými metodami rozšíření.

Konkrétně vygenerovaná statická metoda má atributy, modifikátory a název deklarované metody rozšíření a také seznam parametrů typu, seznam parametrů a seznam omezení zřetězený z deklarace rozšíření a deklarace metody v daném pořadí:

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

Generates:

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

Operátoři

Přestože operátory rozšíření mají explicitní typy operandů, musí být stále deklarovány v rámci deklarace rozšíření:

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

To umožňuje deklarovat a odvozovat parametry typu a je analogické k tomu, jak musí být běžný uživatelem definovaný operátor deklarován v rámci jednoho z jeho typů operandu.

Checking

Odvozovatelnost: Pro každý člen rozšíření bez metody musí být použity všechny parametry typu jeho rozšiřujícího bloku v kombinované sadě parametrů z rozšíření a člena.

Jedinečnost: V rámci dané obklopující statické třídy je sada deklarací členů rozšíření se stejným typem příjemce (modulo konverze identity a substituce jmen parametrů typu) považována za jediný prostor deklarace obdobně jako členové v deklaraci třídy či struktury, a podléhá stejným pravidlům o jedinečnosti.

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

Aplikace tohoto pravidla jedinečnosti zahrnuje klasické rozšiřující metody ve stejné statické třídě. Pro účely porovnání s metodami v rámci deklarací this rozšíření se parametr považuje za specifikaci příjemce spolu s parametry typu, které jsou uvedené v tomto typu příjemce, a zbývající parametry typu a parametry metody se používají pro podpis metody:

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

Spotřeba

Při pokusu o vyhledání člena rozšíření všechny deklarace rozšíření v rámci statických tříd, které jsou usingimportovány jejich členy jako kandidáty bez ohledu na typ příjemce. Pouze jako součást řešení jsou kandidáti s nekompatibilními typy přijímačů zahozeny.
Úplné odvození generického typu se pokouší mezi typem argumentů (včetně skutečného příjemce) a jakýmikoliv parametry typu (v kombinaci s těmi v deklaraci rozšíření a v deklaraci člena rozšíření).
Pokud jsou zadány explicitní argumenty typu, slouží k nahrazení parametrů typu deklarace rozšíření a deklarace člena rozšíření.

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

Podobně jako u klasických rozšiřujících metod lze generované metody implementace vyvolat staticky.
To kompilátoru umožňuje rozlišení mezi členy rozšíření se stejným názvem a aritou.

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

Statické metody rozšíření budou vyřešeny jako metody rozšíření instance (budeme uvažovat o dalším argumentu typu příjemce).
Vlastnosti rozšíření budou vyřešeny stejně jako metody rozšíření, s jedním parametrem (parametr příjemce) a jedním argumentem (skutečná hodnota příjemce).

using static směrnice

Using_static_directive zpřístupňuje členy bloků rozšíření v deklaraci typu pro přístup k rozšíření.

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

Stejně jako dříve lze na přístupné statické členy (s výjimkou rozšiřujících metod) obsažené přímo v deklaraci daného typu odkazovat přímo.
To znamená, že metody implementace (s výjimkou metod rozšíření) lze použít přímo jako statické metody:

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

Using_static_directive stále neimportuje rozšiřující metody přímo jako statické metody, takže metodu implementace pro nestatické metody rozšíření nelze vyvolat přímo jako statickou metodu.

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

PřetíženíResolutionPriorityAttribute

Členy rozšíření v rámci uzavřené statické třídy podléhají stanovení priority podle hodnot ORPA. Uzavřená statická třída je považována za "obsahující typ", který pravidla ORPA zvažují.
Jakýkoli atribut ORPA, který je k dispozici u vlastnosti rozšíření, se zkopíruje do metod implementace pro přístupové objekty vlastnosti, takže při použití těchto přístupových objektů pomocí syntaxe nejednoznačnosti se respektuje stanovení priority.

Vstupní body

Metody rozšiřujících bloků se nekvalifikují jako kandidáti vstupních bodů (viz "7.1 Spuštění aplikace"). Poznámka: Metoda implementace může být stále kandidátem.

Snížení

Strategie snížení pro deklarace rozšíření není rozhodnutí na úrovni jazyka. Kromě implementace sémantiky jazyka však musí splňovat určité požadavky:

  • Formát generovaných typů, členů a metadat by měl být jasně specifikován ve všech případech, aby ho ostatní kompilátory mohly využívat a generovat.
  • Vygenerované artefakty by měly být stabilní v tom smyslu, že rozumné pozdější úpravy by neměly narušit uživatele, kteří se zkompilovali proti dřívějším verzím.

Tyto požadavky vyžadují větší upřesnění při provádění a může být nutné je ohrozit v rohových případech, aby bylo možné zajistit rozumný přístup k implementaci.

Metadata pro deklarace

Cíle

Následující návrh umožňuje:

  • zaokrouhlování symbolů deklarace rozšíření prostřednictvím metadat (úplná a referenční sestavení),
  • stabilní odkazy na členy rozšíření (dokumentace XML),
  • místní určení vygenerovaných názvů (užitečné pro EnC),
  • sledování veřejného rozhraní API.

V případě dokumentace XML je docID člena rozšíření stejné jako docID člena rozšíření v metadatech. Například identifikátor docID použitý v cref="Extension.extension(object).M(int)" je M:Extension.<>E__ExtensionGroupingTypeNameForObject.M(System.Int32) a tento identifikátor docID je stabilní pro opakované kompilace a opětovné řazení bloků rozšíření. V ideálním případě by to také zůstávalo stabilní, když se změní omezení v rámci bloku rozšíření, ale nenašli jsme návrh, který by toho dosáhl, aniž by měl negativní vliv na návrh jazyka v případě konfliktů členů.

Pro EnC je užitečné vědět přímo (pouhým pohledem na upravený člen rozšíření), kde se aktualizovaný člen rozšíření v metadatech zaznamenává.

Pro sledování veřejných API stabilnější pojmenování snižují šum. Po technické stránce by se ale v takových scénářích neměly názvy typů rozšíření seskupení hrát roli. Při pohledu na člena M rozšíření nezáleží na názvu typu seskupení rozšíření, ale na podpisu bloku rozšíření, ke kterému patří. Podpis veřejného rozhraní API by neměl být považován za Extension.<>E__ExtensionGroupingTypeNameForObject.M(System.Int32), ale spíše za Extension.extension(object).M(int). Jinými slovy, členy rozšíření by měly být považovány za dvě sady typových parametrů a dvě sady parametrů.

Přehled

Bloky rozšíření jsou seskupené dle podpisu na úrovni CLR. Každá skupina ekvivalence CLR se vygeneruje jako typ rozšiřujícího seskupení s názvem podle obsahu. Bloky rozšíření ve skupině ekvivalence CLR se pak podskupí podle ekvivalence jazyka C#. Každá ekvivalentní skupina v C# je vygenerována jako typ značky rozšíření s názvem odvozeným od obsahu a vnořena do odpovídajícího typu seskupení rozšíření. Typ značky rozšíření obsahuje jednu metodu značky rozšíření , která kóduje parametr rozšíření. Metoda značky rozšíření a její obsahující typ kódování značky rozšíření kódují podpis bloku rozšíření s plnou věrností. Deklarace každého člena rozšíření je vygenerována ve správném typu seskupení rozšíření, odkazuje na typ značky rozšíření podle jeho názvu prostřednictvím atributu a je doprovázena statickou metodou implementace nejvyšší úrovně s upraveným podpisem.

Tady je schéma s přehledem kódování metadat:

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

Uzavřená statická třída je generována atributem [Extension] .

Podpis na úrovni CLR vs. podpis na úrovni C#

Podpis na úrovni CLR pro blok rozšíření vzniká z:

  • normalizace názvů parametrů typu na T0, T1atd ...
  • odebrání atributů
  • vymazání názvu parametru
  • mazání modifikátorů parametrů (jako ref, in, scoped, ...)
  • mazání názvů řazených kolekcí členů
  • odstraňování anotací nulovatelnosti
  • mazání omezení notnull

Poznámka: Další omezení se zachovají, například new(), struct, class, allows ref struct, unmanaged, a omezení typu.

Typy seskupení rozšíření

Typ seskupení rozšíření je generován do metadat pro každou sadu bloků rozšíření ve zdroji, které mají stejný podpis CLR.

  • Jeho název je nepopisitelný a určený na základě obsahu podpisu na úrovni CLR. Další podrobnosti najdete níže.
  • Parametry typu mají normalizované názvy (T0, T1, ...) a nemají žádné atributy.
  • Je to veřejné a zapečetěné.
  • Je označen příznakem specialname a atributem [Extension] .

Název skupiny rozšíření založený na obsahu je založený na podpisu na úrovni CLR a zahrnuje následující:

  • Plně kvalifikovaný název CLR typu parametru rozšíření.
    • Odkazované názvy parametrů typu budou normalizovány na T0, T1atd ... podle pořadí, ve které se zobrazují v deklaraci typu.
    • Plně kvalifikovaný název nebude zahrnovat sestavení, které ho obsahuje. Mezi sestaveními je běžné přesouvat typy, které by neměly přerušit odkazy na dokumenty XML.
  • Omezení parametrů typu budou zahrnuta a seřazena tak, aby se jejich pořadí ve zdrojovém kódu nezměnilo. Konkrétně:
    • Omezení parametru typu budou uvedena v pořadí deklarace. Omezení parametru Nth type nastane před parametrem typu Nth+1.
    • Omezení typu budou seřazena podle ordinálního porovnávání celých názvů.
    • Omezení bez typu jsou seřazena deterministicky a zpracovávají se tak, aby nedocházelo k nejednoznačnosti nebo kolizi s omezeními typu.
  • Vzhledem k tomu, že neobsahuje atributy, záměrně ignoruje C#-ismy, jako jsou názvy n-tic, vlastnost nulovatelnosti atd.

cs-CZ: Poznámka: U názvu je zaručeno, že zůstane stabilní v rámci rekompilací, zpřeházení a změn v C#-ismu, které nemají vliv na podpis na úrovni CLR.

Typy značek rozšíření

Typ značky znovu deklaruje parametry typu obsahujícího seskupovací typ (typ seskupení rozšíření), abyste získali úplnou věrnost zobrazení bloků rozšíření jazyka C#.

Typ značky rozšíření se vygeneruje do metadat pro každou sadu bloků rozšíření ve zdroji se stejným podpisem na úrovni C#.

  • Jeho název je nevyslovitelný a je určený na základě obsahu podpisu na C# úrovni bloku rozšíření. Další podrobnosti najdete níže.
  • Předepisuje parametry typu pro jeho obsahující typ seskupení tak, aby byly deklarovány ve zdroji (včetně názvu a atributů).
  • Je veřejná a statická.
  • Je označen příznakem specialname .

Název typu značky rozšíření podle obsahu vychází z následujících informací:

  • Názvy parametrů typu budou zahrnuty v pořadí, v jakém se zobrazí v deklaraci rozšíření.
  • Atributy parametrů typu budou zahrnuty a seřazeny tak, aby jejich pořadí ve zdrojovém kódu nezměnilo název.
  • Omezení parametrů typu budou zahrnuta a seřazena tak, aby se jejich pořadí ve zdrojovém kódu nezměnilo.
  • Plně kvalifikovaný název jazyka C# rozšířeného typu
    • To bude obsahovat položky, jako jsou nulovatelné anotace, názvy n-tic, atd.
    • Plně kvalifikovaný název nebude zahrnovat informace o sestavení, které ho obsahuje.
  • Název parametru rozšíření
  • Modifikátory parametru rozšíření (ref, ref readonly, scoped, ...) v deterministickém pořadí
  • Plně kvalifikovaný název a argumenty atributů pro všechny atributy použité na parametr rozšíření v deterministickém pořadí

Poznámka: Název je zaručen, že zůstane stabilní v rámci opětovné kompilace a opětovného řazení.
Poznámka: Typy markerů rozšíření a metody markerů rozšíření se vyemitují jako součást referenčních sestavení.

Metoda značek rozšíření

Účelem metody značky je zakódovat rozšiřující parametr bloku rozšíření. Vzhledem k tomu, že je členem typu značky rozšíření, může odkazovat na znovu deklarované parametry typu značky rozšíření.

Každý typ značky rozšíření obsahuje jednu metodu, metodu značky rozšíření.

  • Jedná se o statickou, negenerickou metodu, která vrací void a je volána <Extension>$.
  • Jediný parametr má atributy – refness, typ a název – převzaté z parametru rozšíření.
    Pokud parametr rozšíření nezadává název, název parametru je prázdný.
  • Je označen příznakem specialname .

Přístupnost metody značek bude nejmenší z omezujících přístupností mezi odpovídajícími deklarovanými členy rozšíření. Pokud žádné nejsou deklarovány, použije se private.

Členové rozšíření

Deklarace metody nebo vlastnosti v bloku rozšíření ve zdroji jsou reprezentovány jako členové typu seskupení rozšíření v metadatech.

  • Podpisy původních metod jsou zachovány (včetně atributů), ale jejich těla jsou nahrazena throw NotImplementedException().
  • Na ty by se nemělo odkazovat v IL.
  • Metody, vlastnosti a jejich přístupové objekty jsou označeny odkazem [ExtensionMarkerName("...")] na název typu značky rozšíření odpovídající bloku rozšíření daného členu.

Metody implementace

Těla metod pro deklarace metod/vlastností v bloku rozšíření ve zdroji jsou generovány jako statické implementační metody ve statické třídě nejvyšší úrovně.

  • Metoda implementace má stejný název jako původní metoda.
  • Obsahuje parametry typu odvozené z rozšiřujícího bloku, které jsou přidány před parametry typu původní metody (včetně atributů).
  • Má stejné atributy a přístupnost jako původní metoda.
  • Pokud implementuje statickou metodu, má stejné parametry a návratový typ.
  • Pokud implementuje metodu instance, má předpenddovaný parametr podpisu původní metody. Atributy, refness, typ a název tohoto parametru jsou odvozeny od parametru rozšíření deklarovaného v příslušném bloku rozšíření.
  • Parametry v metodách implementace se vztahují k typovým parametrům vlastněným metodou implementace, namísto parametrů v bloku rozšíření.
  • Pokud je původním členem běžná metoda instance, je metoda implementace označena atributem [Extension] .

Atribut ExtensionMarkerName

Typ ExtensionMarkerNameAttribute je určen pouze pro použití kompilátoru – ve zdroji není povolený. Deklarace typu je syntetizována kompilátorem, pokud ještě není součástí kompilace.

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

Poznámka: I když jsou některé cíle atributů zahrnuty pro zajištění budoucí kompatibility (vnořené typy rozšíření, pole rozšíření, události rozšíření), AttributeTargets.Constructor není zahrnut, protože konstruktory rozšíření by nebyly považovány za konstruktory.

Příklad

Poznámka: Pro čitelnost používáme zjednodušené názvy založené na obsahu. Poznámka: Protože jazyk C# nemůže reprezentovat opětovnou deklaraci parametru typu, kód představující metadata není platný kód jazyka C#.

Tady je příklad znázorňující fungování seskupování bez členů:

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

se vygeneruje jako

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

Tady je příklad znázorňující, jak se členové vygenerují:

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

se vygeneruje jako

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

Kdykoli se členové rozšíření používají ve zdroji, vygenerujeme je jako odkaz na metody implementace. Například: Vyvolání enumerableOfInt.Method() by se vygenerovalo jako statické volání IEnumerableExtensions.Method<int>(enumerableOfInt).

Dokumentace XML

Komentáře k dokumentu bloku rozšíření se generují pro typ značky (DocID bloku rozšíření je E.<>E__MarkerContentName_For_ExtensionOfT'1 v následujícím příkladu).
Mohou odkazovat na parametr rozšíření a parametry typu pomocí <paramref> a <typeparamref> v uvedeném pořadí).
Poznámka: Parametr rozšíření ani parametry typu (s <param> a <typeparam>) v členu rozšíření nelze zdokumentovat.

Pokud se jako jeden typ značky vygenerují dva bloky rozšíření, jejich komentáře k dokumentu se také sloučí.

Nástroje, které zpracovávají dokumenty XML, odpovídají za kopírování značek <param> a <typeparam> z bloku rozšíření do členů rozšíření dle potřeby (tj. informace o parametrech by měly být zkopírovány pouze pro členy instance).

U metod implementace se vysílá <inheritdoc> a odkazuje na příslušného člena rozšíření pomocí cref. Například metoda implementace pro getter odkazuje na dokumentaci vlastnosti rozšíření. Pokud člen rozšíření nemá komentáře k dokumentu, vynechá se <inheritdoc>.

Nevarujeme momentálně u bloků rozšíření a členů rozšíření, pokud:

  • parametr rozšíření je zdokumentovaný, ale parametry člena rozšíření nejsou
  • nebo naopak
  • nebo v ekvivalentních situacích s nedokumentovanými parametry typu

Například následující komentáře k dokumentu:

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

zadejte následující kód 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>

Odkazy NA CREF

S rozšiřujícími bloky můžeme zacházet jako s vnořenými typy, které je možné řešit jejich podpisem (jako by se jednalo o metodu s jedním parametrem rozšíření). Příklad: E.extension(ref int).M().

Ale cref nemůže řešit samotný blok rozšíření. E.extension(int) může odkazovat na metodu s názvem "extension" v typu 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
  }
}

Vyhledávání ví, že má prohledat všechny odpovídající rozšiřující bloky.
Vzhledem k tomu, že zakážeme nekvalifikované odkazy na členy rozšíření, cref je také zakáže.

Syntaxe by byla následující:

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
  ;

Je chybou použít extension_member_cref na nejvyšší úrovni (extension(int).M) nebo jako součást jiného rozšíření (E.extension(int).extension(string).M).

Zásadní změny

Typy a aliasy nemusí mít název "extension".

Problémy s otevřením

Dočasná část dokumentu týkající se otevřených problémů, včetně diskuze o nedefinované syntaxi a alternativních návrzích
  • Měli bychom upravit požadavky na přijímač při přístupu k členu rozšíření? (komentář)
  • Potvrďte extension vs. extensions jako klíčové slovo (odpověď: extension, LDM 2025-03-24)
  • Potvrďte, že chceme zakázat [ModuleInitializer] (odpověď: ano, zamítnout, LDM 2025-06-11)
  • Ověřte, že jako kandidáti na vstupní bod můžeme zahodit bloky rozšíření (odpověď: ano, zahodit, LDM 2025-06-11)
  • Potvrďte logiku JazykVer (přeskočte nová rozšíření, vs. zvažte je a nahlašujte je při výběru) (odpověď: vytvoření vazby bezpodmínečně a chyby LangVer s výjimkou metod rozšíření instancí, LDM 2025-06-11)
  • Měly by být bloky rozšíření, které se slučují a mají sloučené komentáře k dokumentu, vyžadovány? (odpověď: komentáře k dokumentu se sloučí bezobslužně, když se bloky sloučí, nejsou partial potřeba, potvrzeny e-mailem 2025-09-03)
  • Ověřte, že by členové neměli být pojmenováni po obsahujících nebo rozšířených typech. (odpověď: Ano, potvrzeno e-mailem 2025-09-03)

Revisit grouping/conflict rules in light of přenositelnost issue: https://github.com/dotnet/roslyn/issues/79043

(odpověď: Tento scénář byl vyřešen jako součást nového návrhu metadat s názvy typů založenými na obsahu, je povolený)

Aktuální logika spočívá ve seskupení rozšiřujících bloků, které mají stejný typ přijímače. Toto nezohledňuje omezení. To způsobuje problém s přenositelností v tomto scénáři:

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

Návrhem je použít stejnou logiku seskupení, kterou plánujeme pro návrh typu seskupení rozšíření, konkrétně pro omezení na úrovni CLR (tj. ignorujeme nenull, názvy n-tic, anotace nulovatelnosti).

Má být refness kódován v názvu typu seskupení?

  • Zkontrolujte návrh, který ref není součástí názvu typu seskupení rozšíření (je potřeba další diskuzi po opětovném seznámení se skupinami/konfliktních pravidel, LDM 2025-06-23) (odpověď: potvrzena e-mailem 2025-09-03)
public static class E
{
  extension(ref int)
  {
    public static void M()
  }
}

Vyzařuje se jako:

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

Odkaz na CREF třetí strany pro E.extension(ref int).M je generován jako M:E.<>ExtensionGroupingTypeXYZ.M(). Pokud je ref odebrán nebo přidán do parametru rozšíření, pravděpodobně nechceme narušit CREF.

O tento scénář se moc nezajímáme, protože jakékoli použití jako rozšíření by bylo nejednoznačností:

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

Ale záleží na tomto scénáři (kvůli přenositelnosti a užitečnosti) a po úpravě konfliktních pravidel by to mělo fungovat s navrhovaným návrhem metadat:

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

Nezohlednění refness má nevýhodu, jelikož v tomto scénáři ztrácíme přenositelnost.

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ázev

  • Měli bychom zakázat použití vlastností rozšíření ve funkcích typu nameof, stejně jako to děláme u klasických a nových metod rozšíření? (odpověď: chtěli bychom použít nameof(EnclosingStaticClass.ExtensionMember). Potřebuje návrh, pravděpodobně odložen od .NET 10. LDM 2025-06-11)

konstrukce založené na vzorech

Metody

  • Kde by měly přijít na úlohu nové metody rozšíření? (odpověď: stejná místa, kde se přehrávají klasické rozšiřující metody, LDM 2025-05-05)

Sem patří:

  • GetEnumerator / GetAsyncEnumerator v foreach
  • Deconstruct v dekonstrukci, v pozičním vzoru a foreachu
  • Add v inicializátorech kolekce
  • GetPinnableReference v fixed
  • GetAwaiter v await

To vylučuje:

  • Dispose / DisposeAsync v using a foreach
  • MoveNext / MoveNextAsync v foreach
  • Slice a int indexery v implicitních indexerech (a možná i vzorech seznamů?)
  • GetResult v await

Vlastnosti a indexery

  • Kde by měly být využity rozšiřující vlastnosti a indexery? (odpověď: začněme čtyřmi, LDM 2025-05-05)

Zahrnuli bychom:

  • inicializátor objektů: new C() { ExtensionProperty = ... }
  • inicializátor slovníku: new C() { [0] = ... }
  • with: x with { ExtensionProperty = ... }
  • vzory vlastností: x is { ExtensionProperty: ... }

Vyloučíme:

  • Current v foreach
  • IsCompleted v await
  • Count / Length vlastnosti a indexery v seznamovém vzoru
  • Count / Length vlastnosti a indexy v implicitních indexerech
Vlastnosti vracející delegáta
  • Ověřte, že vlastnosti rozšíření tohoto obrazce se mají uplatnit pouze v dotazech LINQ, stejně jako vlastnosti instance. (odpověď: dává smysl, LDM 2025-04-06)
Seznam a rozprostřený vzor
  • Ověřte, že indexery rozšíření Index/Range by se měly přehrávat ve vzorech seznamu (odpověď: není relevantní pro C# 14).
Navštivte znovu, kde se uplatňují vlastnosti rozšíření Count/Length.

kolekční výrazy

  • Rozšíření Add funguje
  • Rozšíření GetEnumerator funguje pro šíření
  • Rozšíření GetEnumerator nemá vliv na určení typu prvku (musí být instancí).
  • Statické Create metody rozšíření by se neměly počítat jako požehnaná metoda vytvoření
  • Mají počitatelné vlastnosti rozšíření vliv na výrazy v kolekci?

params kolekce

  • Rozšíření Add neovlivňují, jaké typy jsou povoleny s params

výrazy slovníku

  • Ověřte, že indexery rozšíření se ve výrazech slovníku nepoužívají, protože přítomnost indexeru je nedílnou součástí toho, co definuje typ slovníku. (odpověď: není relevantní pro C# 14)

extern

Schéma pojmenování/číslování pro typ rozšíření

Úkol
Aktuální systém číslování způsobuje problémy s ověřováním veřejných rozhraní API, které zajišťuje, aby se veřejná rozhraní API shodovala mezi referenčními sestaveními a implementačními sestaveními.

Měli bychom provést jednu z následujících změn? (odpověď: Zavádíme schéma pojmenování založené na obsahu, abychom zvýšili stabilitu veřejného rozhraní API a nástroje se budou muset aktualizovat tak, aby odpovídaly metodám značek).

  1. upravit nástroj
  2. použití některého schématu pojmenování založeného na obsahu (TBD)
  3. Nechte název řídit pomocí nějaké syntaxe

Nová obecná metoda rozšíření pro přetypování stále nemůže fungovat v LINQ.

Úkol
V dřívějších návrzích rolí/rozšíření bylo možné zadat pouze argumenty typu metody explicitně.
Teď, když se ale zaměřujeme na zdánlivý přechod z klasických rozšiřujících metod, musí být explicitně zadány všechny argumenty typu.
To se nepodaří vyřešit problém s použitím metody cast rozšíření v LINQ.

Měli bychom změnit funkci rozšíření tak, aby vyhovovala tomuto scénáři? (odpověď: Ne, to nezpůsobí, že se vrátíme k návrhu řešení rozšíření, LDM 2025-05-05)

Omezení parametru rozšíření u člena rozšíření

Měli bychom povolit následující? (odpověď: ne, toto by mohlo být přidáno později)

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

Nullovatelnost

  • Potvrďte aktuální návrh, tj. maximální přenositelnost/kompatibilita (odpověď: ano, 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!;
    }

Metadatové informace

  • Měly by metody kostry vyvolat NotSupportedException nebo jinou standardní výjimku (v současné době používáme throw null;)? (odpověď: ano, LDM 2025-04-17)
  • Měli bychom v metadatech přijmout více než jeden parametr v metodě značek (v případě, že nové verze přidávají další informace)? (odpověď: můžeme zůstat striktní, LDM 2025-04-17)
  • Má být značka rozšíření nebo vyslovitelné metody implementace označeny speciálním názvem? (odpověď: Metoda značky by měla být označena speciálním názvem a měli bychom ji zkontrolovat, ale ne metody implementace, LDM 2025-04-17)
  • Měli bychom do statické třídy přidat [Extension] atribut, i když uvnitř neexistuje žádná metoda rozšíření instance? (odpověď: ano, LDM 2025-03-10)
  • Ověřte, že bychom také měli přidat [Extension] atribut k implementaci getters a setters. (odpověď: ne, LDM 2025-03-10)
  • Ověřte, že mají být typy rozšíření označené speciálním názvem a kompilátor bude vyžadovat tento příznak v metadatech (jedná se o zásadní změnu z verze Preview) ( odpověď: schváleno, LDM 2025-06-23)

Scénář statické továrny

  • Jaká jsou konfliktní pravidla statických metod? (odpověď: použijte existující pravidla jazyka C# pro uzavřený statický typ, bez uvolnění, LDM 2025-03-17)

Vyhledat

  • Jak vyřešit vyvolání metody instance teď, když máme vyslovovatelné názvy implementace? Dáváme přednost metodě kostry před odpovídající implementační metodou.
  • Jak vyřešit statické metody rozšíření? (odpověď: stejně jako metody rozšíření instance, LDM 2025-03-03)
  • Jak řešit vlastnosti? (zodpovězeno v hrubých rysech LDM 2025-03-03, ale potřebuje pokračovat pro lepší zlepšení)
  • Určování rozsahu a pravidla stínování pro parametry rozšíření a parametry typu (odpověď: v rozsahu bloku rozšíření, stínování zakázáno, LDM 2025-03-10)
  • Jak by se funkce ORPA měla vztahovat na nové metody rozšíření? (odpověď: považovat rozšiřující bloky za transparentní, "obsahující typ" pro ORPA je uzavřená statická třída, LDM 2025-04-17)
public static class Extensions
{
    extension(Type1)
    {
        [OverloadResolutionPriority(1)]
        public void Overload(...)
    }
    extension(Type2)
    {
        public void Overload(...)
    }
}
  • Má se ORPA použít u nových vlastností rozšíření? (odpověď: Ano a ORPA by měla být zkopírována do metod implementace, LDM 2025-04-23)
public static class Extensions
{
    extension(int[] i)
    {
        public P { get => }
    }
    extension(ReadOnlySpan<int> r)
    {
       [OverloadResolutionPriority(1)]
       public P { get => }
    }
}
  • Jak přepracovat pravidla rozlišení klasických rozšíření? Děláme to
    1. aktualizujte standard pro klasické metody rozšíření a použijte ho k popisu nových metod rozšíření,
    2. Ponechte stávající jazyk pro klasické rozšiřující metody, použijte ho také k popisu nových rozšiřujících metod, ale oba mají známou odchylku od specifikace.
    3. Zachovat stávající jazyk pro klasické rozšiřující metody, ale použít jiný jazyk pro nové rozšiřující metody a ponechat pouze známou odchylku specifikace u klasických rozšiřujících metod.
  • Ověřte, že chceme zakázat explicitní argumenty typů pro přístup k vlastnosti (odpověď: žádný přístup k vlastnostem s explicitními argumenty typu, probíraný v WG)
string s = "ran";
_ = s.P<object>; // error

static class E
{
    extension<T>(T t)
    {
        public int P => 0;
    }
}
  • Ověřte, že chceme, aby se pravidla pro zlepšení použila i v případě, že příjemce je typ (odpověď: parametr rozšíření pouze typu by se měl zvážit při řešení statických členů rozšíření, 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;
    }
}
  • Ověřte, že nám vyhovuje nejednoznačnost, když jsou použitelné jak metody, tak vlastnosti (odpověď: měli bychom připravit návrh, který by byl lepší než současný stav, s plánem na .NET 10 a LDM ze dne 2025-06-23)
  • Než určíme druh vítězného člena, potvrďte, že nechceme mít lepší výkonnost pro všechny členy (odpověď: vynechání z .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;
    }
}
  • Máme implicitní příjemce v rámci deklarací rozšíření? (odpověď: ne, dříve probírané v LDM)
static class E
{
    extension(object o)
    {
        public void M() 
        {
            M2();
        }
        public void M2() { }
    }
}
  • Měli bychom povolit vyhledávání u parametru typu? (diskuse) (odpověď: Ne, budeme čekat na zpětnou vazbu, LDM 2025-04-16)

Přístupnost

  • Jaký je význam přístupnosti v rámci deklarace rozšíření? (odpověď: Deklarace rozšíření se nezapočítávají jako rozsah přístupnosti, LDM 2025-03-17)
  • Měli bychom u parametru příjemce použít kontrolu "nekonzistentní přístupnosti" i pro statické členy? (odpověď: ano, 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 { }
}

Ověření deklarace rozšíření

  • Měli bychom uvolnit ověření parametrů typu (odvození: všechny parametry typu musí být uvedeny v typu parametru rozšíření), pokud existují pouze metody? (odpověď: ano, LDM 2025-04-06) To by umožnilo portování 100% klasických metod rozšíření.
    Pokud máte TResult M<TResult, TSource>(this TSource source), můžete ho přenést jako extension<TResult, TSource>(TSource source) { TResult M() ... }.

  • Ověřte, zda by měly být povoleny inicializátory pouze pro inicializaci v rozšířeních (odpověď: prozatím je v pořádku je zakázat, LDM 2025-04-17)

  • Měl by být povolen pouze jediný rozdíl v "ref-ness" přijímače extension(int receiver) { public void M2() {} }extension(ref int receiver) { public void M2() {} }? (odpověď: ne, zachovat specifikované pravidlo, LDM 2025-03-24)

  • Měli bychom si stěžovat na konflikt podobný tomuto extension(object receiver) { public int P1 => 1; }extension(object receiver) { public int P1 {set{}} }? (odpověď: ano, zachovat specifikované pravidlo, LDM 2025-03-24)

  • Měli bychom si stěžovat na konflikty mezi kostrovými metodami, které nejsou v konfliktu s metodami implementace? (odpověď: ano, zachovat specifikované pravidlo, LDM 2025-03-24)

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

Aktuální konfliktní pravidla jsou: 1. Pomocí pravidel třídy/struktury zkontrolujte, zda nedochází k žádným konfliktům v podobných rozšířeních. Ujistěte se, že nedochází ke konfliktu mezi metodami implementace a různými deklaracemi rozšíření.

  • Potřebujeme stále první část pravidel? (odpověď: Ano, tuto strukturu zachováváme, protože pomáhá s využitím rozhraní API, LDM 2025-03-24)

Dokumentace XML

  • Je paramref u členů rozšíření podporován parametr příjemce? Dokonce i na statické? Jak se kóduje ve výstupu? Existuje riziko, že některé existující nástroje nebudou spokojené, když ho nenajdou mezi parametry API, i když pravděpodobně běžný způsob <paramref name="..."/> bude fungovat pro člověka. (odpověď: Ano parametr paramref na parametr rozšíření je povolený pro členy rozšíření, LDM 2025-05-05)
  • Máme kopírovat komentáře k dokumentu do metod implementace s mluvenými názvy? (odpověď: bez kopírování, LDM 2025-05-05)
  • Měl by <param> se element odpovídající parametru receiveru zkopírovat z kontejneru rozšíření pro metody instance? Cokoliv jiného by mělo být zkopírováno z kontejneru do metod implementace (<typeparam> atd.)? (odpověď: bez kopírování, LDM 2025-05-05)
  • <param> být parametr rozšíření povolený pro členy rozšíření jako přepsání? (odpověď: ne, prozatím, LDM 2025-05-05)
  • Zobrazí se souhrn bloků rozšíření kdekoli?

CREF

  • Potvrďte syntaxi (odpověď: návrh je dobrý, LDM 2025-06-09)
  • Je možné odkazovat na blok rozšíření (E.extension(int))? (odpověď: ne, LDM 2025-06-09)
  • Je možné odkazovat na člena pomocí nekvalifikované syntaxe: extension(int).Member (odpověď: ano, LDM 2025-06-09)
  • Měli bychom pro nevyslovitelné jméno použít různé znaky, abychom se vyhnuli escapování XML? (odpověď: odložit na pracovní skupinu, LDM 2025-06-09)
  • Ověřte, že je v pořádku, že oba odkazy na kostru a metody implementace jsou možné: E.M vs. E.extension(int).M. Oba se zdají být nezbytné (vlastnosti rozšíření a přenositelnost klasických rozšiřujících metod). (odpověď: ano, LDM 2025-06-09)
  • Jsou názvy metadat rozšíření problematické pro verzování dokumentů? (odpověď: Ano, přesuneme se od ordinalů a použijeme stabilní schéma pojmenování založené na obsahu)

Přidání podpory pro další druhy členů

Nemusíme implementovat celý tento návrh najednou, ale můžeme k němu přistupovat jeden nebo několik druhů členů najednou. Na základě známých scénářů v základních knihovnách bychom měli pracovat v následujícím pořadí:

  1. Vlastnosti a metody (instance a statické)
  2. Operátoři
  3. Indexery (instance a statická, mohou být v dřívějším bodě oportunisticky provedeny)
  4. Cokoli jiného

Kolik chceme předem načíst návrh pro ostatní druhy členů?

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
    ;

Vnořené typy

Pokud se rozhodneme přejít vpřed s vnořenými typy rozšíření, tady jsou některé poznámky z předchozích diskuzí:

  • Došlo by ke konfliktu, pokud by dvě deklarace rozšíření deklarovaly vnořené typy rozšíření se stejnými názvy a aritou. Nemáme řešení pro reprezentaci v metadatech.
  • Hrubý přístup, který jsme probírali pro metadata:
    1. Vnořený typ kostry bychom vygenerovali s původními parametry typu a bez členů.
    2. Vnořený typ implementace s předpendovanými parametry typu bychom vygenerovali z deklarace rozšíření a všechny implementace členů, jak se zobrazují ve zdroji (odkazy moduluo na parametry typu)

Konstruktory

Konstruktory jsou obecně popsány jako člen instance v jazyce C#, protože jejich tělo má přístup k nově vytvořené hodnotě prostřednictvím klíčového this slova. To ale nefunguje dobře pro přístup založený na parametrech na členy rozšíření instance, protože neexistuje žádná předchozí hodnota, která by se předala jako parametr.

Místo toho konstruktory rozšíření fungují spíše jako statické metody továrny. Považují se za statické členy v tom smyslu, že nezávisí na názvu parametru příjemce. Jejich těla musí explicitně vytvořit a vrátit výsledek konstrukce. Samotný člen je stále deklarován pomocí syntaxe konstruktoru, ale nemůže mít this ani base inicializátory a nespoléhá na typ příjemce s přístupnými konstruktory.

To také znamená, že konstruktory rozšíření lze deklarovat pro typy, které nemají vlastní konstruktory, jako jsou rozhraní a výčtové typy:

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

Allows:

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

Kratší formuláře

Navrhovaný návrh zabraňuje opakování specifikací přijímače pro jednotlivé členy, ale končí vnořením členů rozšíření do statické třídy a deklarace rozšíření. Statické třídy budou pravděpodobně společné, aby obsahovaly pouze jednu deklaraci rozšíření nebo deklarace rozšíření, které budou obsahovat pouze jeden člen, a zdá se, že je pro nás přijatelné povolit syntaktické zkratky těchto případů.

Sloučení deklarací statické třídy a rozšíření:

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

Vypadá to podobně, jako když voláme přístup založený na typu, kde se kontejner pro členy rozšíření jmenuje sám.

Sloučení deklarace rozšíření a člen rozšíření:

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

Vypadá to podobně, jako bychom volali přístup založený na členech, kde každý člen rozšíření obsahuje vlastní specifikaci příjemce.