Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
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
specialnamea 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.
- Odkazované názvy parametrů typu budou normalizovány na
- 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.<>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.<>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.<>E__MarkerContentName_For_ExtensionOfT`1.P">
<summary>Summary for P</summary>
</member>
<member name="M:E.M``2(``0,``1)">
<inheritdoc cref="M:E.<>E__MarkerContentName_For_ExtensionOfT`1.M``1(``0)"/>
</member>
<member name="M:E.get_P``1(``0)">
<inheritdoc cref="P:E.<>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(odpověď:extensionvs.extensionsjako klíčové slovoextension, LDM 2025-03-24) -
Potvrďte, že chceme zakázat(odpověď: ano, zamítnout, LDM 2025-06-11)[ModuleInitializer] -
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čí, nejsoupartialpotř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ý(odpověď: potvrzena e-mailem 2025-09-03)refnení 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)
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/GetAsyncEnumeratorvforeach -
Deconstructv dekonstrukci, v pozičním vzoru a foreachu -
Addv inicializátorech kolekce -
GetPinnableReferencevfixed -
GetAwaitervawait
To vylučuje:
-
Dispose/DisposeAsyncvusingaforeach -
MoveNext/MoveNextAsyncvforeach -
Sliceaintindexery v implicitních indexerech (a možná i vzorech seznamů?) -
GetResultvawait
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:
-
Currentvforeach -
IsCompletedvawait -
Count/Lengthvlastnosti a indexery v seznamovém vzoru -
Count/Lengthvlastnosti 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/Rangeby 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í
Addfunguje - Rozšíření
GetEnumeratorfunguje pro šíření - Rozšíření
GetEnumeratornemá vliv na určení typu prvku (musí být instancí). - Statické
Createmetody 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í
Addneovlivňují, jaké typy jsou povoleny sparams
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
-
Plánujeme umožnit(odpověď: schválená, LDM 2025-06-23)externpřenositelnost: https://github.com/dotnet/roslyn/issues/78572
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).
- upravit nástroj
- použití některého schématu pojmenování založeného na obsahu (TBD)
- 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(odpověď: ano, LDM 2025-04-17)NotSupportedExceptionnebo jinou standardní výjimku (v současné době používámethrow null;)?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(odpověď: ano, LDM 2025-03-10)[Extension]atribut, i když uvnitř neexistuje žádná metoda rozšíření instance?Ověřte, že bychom také měli přidat(odpověď: ne, LDM 2025-03-10)[Extension]atribut k implementaci getters a setters.-
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
- aktualizujte standard pro klasické metody rozšíření a použijte ho k popisu nových metod rozšíření,
- 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.
- 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áteTResult M<TResult, TSource>(this TSource source), můžete ho přenést jakoextension<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(odpověď: ne, zachovat specifikované pravidlo, LDM 2025-03-24)extension(int receiver) { public void M2() {} }extension(ref int receiver) { public void M2() {} }?Měli bychom si stěžovat na konflikt podobný tomuto(odpověď: ano, zachovat specifikované pravidlo, LDM 2025-03-24)extension(object receiver) { public int P1 => 1; }extension(object receiver) { public int P1 {set{}} }?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(odpověď: Ano parametr paramref na parametr rozšíření je povolený pro členy rozšíření, LDM 2025-05-05)paramrefu č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.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(odpověď: bez kopírování, LDM 2025-05-05)<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.)?Má(odpověď: ne, prozatím, LDM 2025-05-05)<param>být parametr rozšíření povolený pro členy rozšíření jako přepsání?- 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í ((odpověď: ne, LDM 2025-06-09)E.extension(int))?Je možné odkazovat na člena pomocí nekvalifikované syntaxe:(odpověď: ano, LDM 2025-06-09)extension(int).Member- 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é:(odpověď: ano, LDM 2025-06-09)E.Mvs.E.extension(int).M. Oba se zdají být nezbytné (vlastnosti rozšíření a přenositelnost klasických rozšiřujících metod).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í:
- Vlastnosti a metody (instance a statické)
- Operátoři
- Indexery (instance a statická, mohou být v dřívějším bodě oportunisticky provedeny)
- 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:
- Vnořený typ kostry bychom vygenerovali s původními parametry typu a bez členů.
- 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.
C# feature specifications