Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
Opmerking
Dit artikel is een functiespecificatie. De specificatie fungeert als het ontwerpdocument voor de functie. Het bevat voorgestelde specificatiewijzigingen, samen met informatie die nodig is tijdens het ontwerp en de ontwikkeling van de functie. Deze artikelen worden gepubliceerd totdat de voorgestelde specificaties zijn voltooid en opgenomen in de huidige ECMA-specificatie.
Er kunnen enkele verschillen zijn tussen de functiespecificatie en de voltooide implementatie. Deze verschillen worden vastgelegd in de relevante LDM-notities (Language Design Meeting).
Meer informatie over het proces voor het aannemen van functiespeclets in de C#-taalstandaard vindt u in het artikel over de specificaties.
Kampioensprobleem: https://github.com/dotnet/csharplang/issues/8697
Verklaring
Syntaxis
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?
;
Uitbreidingsdeclaraties worden alleen gedeclareerd in niet-generieke, niet-geneste statische klassen.
Het is een fout wanneer een type de naam extension krijgt.
Bereikregels
De typeparameters en ontvangerparameter van een uitbreidingsdeclaratie vallen binnen het bereik van de hoofdtekst van de uitbreidingsdeclaratie. Het is een fout om te verwijzen naar de ontvangerparameter vanuit een statisch lid, behalve binnen een nameof expressie. Het is voor leden een fout om typeparameters of parameters te declareren (evenals lokale variabelen en lokale functies direct in de hoofdinhoud van het lid) met dezelfde naam als een typeparameter of ontvangerparameter van de extensiedeclaratie.
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`
}
}
Het is geen fout dat de leden zelf dezelfde naam hebben als de typeparameters of ontvangerparameter van de omringende extensieverklaring. Ledennamen worden niet rechtstreeks gevonden in een eenvoudige naamzoekactie vanuit de uitbreidingsdeclaratie; opzoekactie zal dus de typeparameter of ontvangerparameter van die naam vinden, in plaats van het lid.
Leden zorgen ervoor dat statische methoden direct in de omliggende statische klasse worden gedeclareerd, en deze kunnen worden gevonden via een eenvoudige naamzoekopdracht; echter, een extensiedeclaratietype-parameter of ontvangerparameter met dezelfde naam wordt als eerste gevonden.
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
}
}
Statische klassen als extensiecontainers
Extensies worden gedeclareerd binnen niet-generieke statische klassen op het hoogste niveau, net zoals huidige extensiemethoden, en kunnen daarom naast klassieke extensiemethoden en niet-extensie statische leden bestaan.
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) { ... }
}
Uitbreidingsdeclaraties
Een uitbreidingsdeclaratie is anoniem en biedt een ontvangerspecificatie met eventuele bijbehorende typeparameters en beperkingen, gevolgd door een set uitbreidingsliddeclaraties. De ontvangerspecificatie kan zich in de vorm van een parameter bevinden, of - als alleen statische extensieleden worden gedeclareerd - een type:
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) { ... }
}
}
Het type in de ontvangerspecificatie wordt aangeduid als het ontvangertype en de parameternaam, indien aanwezig, wordt aangeduid als de ontvangerparameter.
Als de ontvangerparameter de naam heeft, is het ontvangertype mogelijk niet statisch.
De ontvangerparameter mag geen modifiers hebben als deze naamloos is, en het mag alleen de refness-modificaties hieronder en scoped anderszins bevatten.
De ontvangerparameter heeft dezelfde beperkingen als de eerste parameter van een klassieke extensiemethode.
Het [EnumeratorCancellation] kenmerk wordt genegeerd als het wordt geplaatst op de ontvangerparameter.
Uitbreidingsleden
Declaraties van extensieleden zijn syntactisch identiek aan de bijbehorende instantie en statische leden in klasse- en structdeclaraties (met uitzondering van constructors). Instantieleden verwijzen naar het object met de naam van de ontvangerparameter.
public static class Enumerable
{
extension(IEnumerable source)
{
// 'source' refers to receiver
public bool IsEmpty => !source.GetEnumerator().MoveNext();
}
}
Het is een fout om een instantie-extensielid op te geven als de omsluitende extensiedeclaratie geen ontvangerparameter specificeert.
public static class Enumerable
{
extension(IEnumerable) // No parameter name
{
public bool IsEmpty => true; // Error: instance extension member not allowed
}
}
Het is een fout om de volgende modifiers op te geven voor een lid van een uitbreidingsdeclaratie: abstract, , virtualoverride, new, , sealeden partialprotected (en gerelateerde toegankelijkheidsaanpassingen).
Het is een fout om de readonly wijzigingsfunctie op te geven voor een lid van een uitbreidingsdeclaratie.
Eigenschappen in extensiedeclaraties mogen geen init accessoren hebben.
Instantieleden zijn niet toegestaan als de ontvangerparameter naamloos is.
Alle leden hebben namen die verschillen van de naam van de statische insluitklasse en de naam van het uitgebreide type, indien deze een heeft.
Het is een fout om een uitbreidingslid te versieren met het [ModuleInitializer] kenmerk.
Refness
Standaard wordt de ontvanger op waarde doorgegeven aan de leden van instantie-uitbreidingen, net als andere parameters.
Een ontvanger van de uitbreidingsdeclaratie in de vorm van een parameter kan echter ref, ref readonly en in specificeren, zolang het ontvangertype bekend is als een waardetype.
Nullbaarheid en attributen
Ontvangerstypen kunnen null-referentietypen zijn of bevatten, en ontvangersspecificaties die de vorm van parameters hebben, kunnen kenmerken opgeven:
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);
}
}
Compatibiliteit met klassieke extensiemethoden
Methoden voor exemplaaruitbreiding genereren artefacten die overeenkomen met de artefacten die zijn geproduceerd door klassieke extensiemethoden.
In het bijzonder heeft de gegenereerde statische methode de attributen, modifiers en naam van de gedeclareerde extensiemethode, evenals de typeparameterlijst, parameterlijst en beperkingslijst, samengevoegd uit de extensiedeclaratie en de methodedeclaratie in die volgorde.
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) { ... }
}
}
Genereert:
[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) { ... }
}
Bedieners
Hoewel extensieoperators expliciete operandentypen hebben, moeten ze nog steeds worden gedeclareerd binnen een uitbreidingsdeclaratie:
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) { ... }
}
}
Hiermee kunnen typeparameters worden gedeclareerd en afgeleid, en is vergelijkbaar met de manier waarop een reguliere door de gebruiker gedefinieerde operator moet worden gedeclareerd binnen een van de operandentypen.
Controleren
Uitstelbaarheid: Voor elk niet-methode-extensielid moeten alle typeparameters van het extensieblok worden gebruikt in de gecombineerde set parameters van de extensie en het lid.
Uniciteit: Binnen een bepaalde statische klasse worden de declaraties van uitbreidingsleden met hetzelfde type ontvanger (modulo-identiteitsconversie en vervanging van parameternaam van het type) behandeld als één declaratieruimte die vergelijkbaar is met de leden binnen een klasse- of structdeclaratie, en zijn onderworpen aan dezelfde regels over uniekheid.
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
}
}
De toepassing van deze uniekheidsregel bevat klassieke extensiemethoden binnen dezelfde statische klasse.
Ter vergelijking met methoden binnen uitbreidingsdeclaraties wordt de this parameter behandeld als een ontvangerspecificatie, samen met alle typeparameters die in dat ontvangertype worden genoemd, en de resterende typeparameters en methodeparameters worden gebruikt voor de methodehandtekening:
public static class Enumerable
{
public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source) { ... }
extension(IEnumerable source)
{
IEnumerable<TResult> Cast<TResult>() { ... } // Error! Duplicate declaration
}
}
Consumptie
Wanneer een opzoekactie voor een extensielid wordt uitgevoerd, leveren alle uitbreidingsdeclaraties binnen statische klassen die using-geïmporteerd zijn hun leden als kandidaten, ongeacht het type ontvanger. Alleen als onderdeel van de resolutie zijn kandidaten met incompatibele ontvangertypen verwijderd.
Een volledige algemene typedeductie wordt geprobeerd tussen het type van de argumenten (inclusief de werkelijke ontvanger) en alle typeparameters (waarbij deze worden gecombineerd in de uitbreidingsdeclaratie en in de declaratie van het extensielid).
Wanneer expliciete typeargumenten worden opgegeven, worden ze gebruikt om de typeparameters van de uitbreidingsdeclaratie en de declaratie van het extensielid te vervangen.
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) { ... }
}
}
Net als bij klassieke extensiemethoden kunnen de gegenereerde implementatiemethoden statisch worden aangeroepen.
Hierdoor kan de compiler onderscheid maken tussen extensieleden met dezelfde naam en arity.
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;
}
}
Statische extensiemethoden worden opgelost, zoals methoden voor instantieuitbreiding (we overwegen een extra argument van het ontvangertype).
Extensie-eigenschappen worden opgelost zoals extensiemethoden, met één parameter (de ontvangerparameter) en één argument (de werkelijke ontvangerwaarde).
using static Richtlijnen
Een using_static_directive maakt leden van extensieblokken in de typedeclaratie beschikbaar voor extensietoegang.
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 { }
Net als voorheen kunnen de toegankelijke statische leden (behalve uitbreidingsmethoden) rechtstreeks in de declaratie van het opgegeven type worden verwezen.
Dit betekent dat implementatiemethoden (met uitzondering van methoden voor extensies) rechtstreeks als statische methoden kunnen worden gebruikt:
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; }
}
}
Een using_static_directive importeert extensiemethoden nog steeds niet rechtstreeks als statische methoden, dus de implementatiemethode voor niet-statische extensiemethoden kan niet rechtstreeks als statische methode worden aangeroepen.
using static E;
M(1); // error: The name 'M' does not exist in the current context
static class E
{
extension(int i)
{
public void M() { }
}
}
OverloadResolutionPriorityAttribute
Extensieleden binnen een omhullende statische klasse zijn onderhevig aan prioritering volgens ORPA-waarden. De omsluitende statische klasse wordt beschouwd als het 'bevattende type' waarmee ORPA-regels rekening houden.
Elk ORPA-kenmerk dat aanwezig is op een extensie-eigenschap wordt gekopieerd naar de implementatiemethoden voor de accessors van de eigenschap, zodat de prioriteitsaanduiding wordt gerespecteerd wanneer deze accessors worden gebruikt via een ondubbelzinnige syntaxis.
Toegangspunten
Methoden van extensieblokken kwalificeren niet als toegangspuntkandidaten (zie "7.1 Opstarten van toepassingen"). Opmerking: de implementatiemethode kan nog steeds een kandidaat zijn.
Verlaging
De lagere strategie voor uitbreidingsdeclaraties is geen beslissing op taalniveau. Behalve het implementeren van de taalsemantiek moet het echter voldoen aan bepaalde vereisten:
- De indeling van gegenereerde typen, leden en metagegevens moet duidelijk worden opgegeven in alle gevallen, zodat andere compilers deze kunnen gebruiken en genereren.
- De gegenereerde artefacten moeten stabiel zijn, in de zin dat redelijke latere wijzigingen consumenten die zijn gecompileerd op basis van eerdere versies, niet mogen breken.
Deze vereisten hebben meer verfijning nodig in de loop van de implementatie en moeten mogelijk in randgevallen worden versoepeld om een praktische implementatie te kunnen realiseren.
Metagegevens voor declaraties
Doelstellingen
Met het onderstaande ontwerp kunt u het volgende doen:
- declaratiesymbolen van extensies afronden via metagegevens (volledige assembly's en referentieassembly's),
- stabiele verwijzingen naar extensieleden (XML-documenten),
- lokale bepaling van verzonden namen (nuttig voor EnC),
- openbare API-tracering.
Voor XML-documenten is de docID voor een extensielid de docID voor het extensielid in metagegevens. De docID die in cref="Extension.extension(object).M(int)" wordt gebruikt, is M:Extension.<>E__ExtensionGroupingTypeNameForObject.M(System.Int32) bijvoorbeeld en dat docID stabiel is in hercompilaties en opnieuw ordenen van extensieblokken. Idealiter zou het ook stabiel blijven wanneer beperkingen op het uitbreidingsblok veranderen, maar we hebben geen ontwerp gevonden dat dat zou bereiken zonder nadelige gevolgen voor taalontwerp voor lidconflicten.
Voor EnC is het handig om lokaal te weten (alleen door naar een gewijzigd extensielid te kijken) waarbij het bijgewerkte extensielid wordt verzonden in metagegevens.
Voor het traceren van publieke API's verminderen stabiele namen de ruis. Maar technisch gezien moeten de namen van groeperingstypen voor extensies niet aan bod komen in dergelijke scenario's. Wanneer u een extensielid Mbekijkt, maakt het niet uit wat de naam is van het type extensiegroepering, wat belangrijk is de handtekening van het extensieblok waartoe het behoort. De openbare API-handtekening mag niet worden gezien als Extension.<>E__ExtensionGroupingTypeNameForObject.M(System.Int32) maar als Extension.extension(object).M(int). Met andere woorden, extensieleden moeten worden gezien als twee sets typeparameters en twee sets parameters.
Overzicht
Extensieblokken worden gegroepeerd op basis van hun handtekening op CLR-niveau. Elke CLR-equivalentiegroep wordt verzonden als een extensiegroeperingstype met een naam op basis van inhoud. Extensieblokken binnen een CLR-equivalentiegroep worden vervolgens subgroepeerd op C#-equivalentie. Elke C#-equivalentiegroep wordt verzonden als een extensiemarkeringstype met een naam op basis van inhoud, genest in het bijbehorende type extensiegroepering. Een type extensiemarkering bevat één extensiemarkeringsmethode waarmee een extensieparameter wordt gecodeerd. De extensiemarkermethode met het bijbehorende extensiemarkertype codeert de handtekening van een extensieblok volledig nauwkeurig. Declaratie van elk extensielid wordt verzonden in het juiste type uitbreidingsgroepering, verwijst terug naar een type extensiemarkering op basis van de naam ervan via een kenmerk en wordt vergezeld van een statische implementatiemethode op het hoogste niveau met een gewijzigde handtekening.
Hier volgt een schematisch overzicht van metagegevenscodering:
[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 ...
}
De omsluitende statische klasse wordt verzonden met een [Extension] kenmerk.
HANDTEKENING op CLR-niveau versus handtekening op C#-niveau
De CLR-handtekening van een uitbreidingsblok resulteert in:
- het normaliseren van parameternamen van het type in
T0,T1enzovoort ... - kenmerken verwijderen
- de parameternaam wissen
- parametermodificaties verwijderen (zoals
ref,in,scoped, ...) - tuplenamen wissen
- nullabiliteitsannotaties wissen
- beperkingen wissen
notnull
Opmerking: andere beperkingen blijven behouden, zoals new(), struct, class, , allows ref structen unmanagedtypebeperkingen.
Typen uitbreidingsgroepen
Er wordt een type extensiegroepering verzonden naar metagegevens voor elke set extensieblokken in de bron met dezelfde CLR-handtekening.
- De naam ervan is onuitsprekelijk en wordt bepaald op basis van de inhoud van de CLR-handtekening. Meer informatie hieronder.
- De typeparameters hebben genormaliseerde namen (
T0,T1, ...) en hebben geen kenmerken. - Het is openbaar en verzegeld.
- Deze wordt gemarkeerd met de
specialnamevlag en een[Extension]kenmerk.
De naam op basis van inhoud van het type extensiegroepering is gebaseerd op de handtekening op CLR-niveau en bevat het volgende:
- De volledig gekwalificeerde CLR-naam van het type extensieparameter.
- Namen van parameternamen waarnaar wordt verwezen, worden genormaliseerd naar
T0,T1enzovoort ... op basis van de volgorde die ze in de typedeclaratie worden weergegeven. - De volledig gekwalificeerde naam zal de omvattende assembly niet bevatten. Het is gebruikelijk dat typen worden verplaatst tussen assembly's en die de XML-documentverwijzingen niet mogen verbreken.
- Namen van parameternamen waarnaar wordt verwezen, worden genormaliseerd naar
- Beperkingen van typeparameters worden opgenomen en gesorteerd, zodat het herschikken ervan in de broncode de naam niet verandert. Specifiek:
- Typeparameterbeperkingen worden weergegeven in de declaratievolgorde. De beperkingen voor de parameter Nth-type vinden plaats vóór de Nth+1-typeparameter.
- Typebeperkingen worden gesorteerd door de volledige namen ordinaal te vergelijken.
- Niet-typebeperkingen worden deterministisch gerangschikt en worden afgehandeld om dubbelzinnigheid of conflicten met typebeperkingen te voorkomen.
- Aangezien dit geen attributen bevat, negeert het opzettelijk specifieke C#-eigenschappen zoals tuple namen, nullwaardigheid, enzovoort...
Opmerking: De naam blijft gegarandeerd stabiel in hercompilaties, hervolgordes en wijzigingen van C#-isms (d.w.w.v. die geen invloed hebben op de handtekening op CLR-niveau).
Extensiemarkeringstypen
Het markeringstype herdeclareert de typeparameters van het omhullende groepeertype (een extensiegroeperingstype) om volledige getrouwheid te krijgen van de C#-weergave van extensieblokken.
Er wordt een type extensiemarkering verzonden naar metagegevens voor elke set extensieblokken in de bron met dezelfde handtekening op C#-niveau.
- De naam is niet zichtbaar en wordt bepaald op basis van de inhoud van de handtekening op C#-niveau van het extensieblok. Meer informatie hieronder.
- Hiermee worden de typeparameters voor het bijbehorende groepeertype opnieuw gedeclareerd als de parameters die in de bron zijn gedeclareerd (inclusief naam en kenmerken).
- Het is openbaar en statisch.
- Deze is gemarkeerd met de
specialnamevlag.
De naam op basis van inhoud van het type extensiemarkering is gebaseerd op het volgende:
- De namen van typeparameters worden opgenomen in de volgorde waarin ze worden weergegeven in de uitbreidingsdeclaratie
- De kenmerken van typeparameters worden opgenomen en gesorteerd, zodat de volgorde ervan in de broncode niet wordt gewijzigd.
- Beperkingen van typeparameters worden opgenomen en gesorteerd, zodat het herschikken ervan in de broncode de naam niet verandert.
- De volledig gekwalificeerde C#-naam van het uitgebreide type
- Dit omvat items zoals null-annotaties, tuple-namen, enzovoort...
- De volledig gekwalificeerde naam bevat niet de insluitingsassembly
- De naam van de extensieparameter
- De modifiers van de extensieparameter (
ref,ref readonly,scoped, ...) in een deterministische volgorde - De volledig gekwalificeerde naam- en kenmerkargumenten voor kenmerken die zijn toegepast op de extensieparameter in een deterministische volgorde
Opmerking: De naam blijft gegarandeerd stabiel bij het opnieuw compileren en opnieuw ordenen.
Opmerking: extensiemarkeringstypen en extensiemarkeringsmethoden worden verzonden als onderdeel van referentieassembly's.
Methode voor extensiemarkering
Het doel van de markeringsmethode is om de extensieparameter van het extensieblok te coderen. Omdat het lid is van het extensiemarkeringstype, kan deze verwijzen naar de opnieuw gedeclareerde typeparameters van het extensiemarkeringstype.
Elk type extensiemarkering bevat één methode, de extensiemarkeringsmethode.
- Het is statisch, niet-algemeen, void-returning, en wordt genoemd
<Extension>$. - De enkelvoudige parameter heeft de kenmerken, refness, type en naam van de extensieparameter.
Als de extensieparameter geen naam opgeeft, is de parameternaam leeg. - Deze is gemarkeerd met de
specialnamevlag.
Toegankelijkheid van de markeringsmethode is de minst beperkende toegankelijkheid tussen de bijbehorende gedeclareerde extensieleden, private wordt gebruikt als er geen worden gedeclareerd.
Uitbreidingsleden
Declaraties van methoden/eigenschappen in een extensieblok in de bron worden weergegeven als leden van het extensiegroeperingstype in metagegevens.
- De signaturen van de oorspronkelijke methoden worden behouden (inclusief attributen), maar de inhoud wordt vervangen door
throw NotImplementedException(). - Deze mogen niet worden verwezen in IL.
- Methoden, eigenschappen en hun accessors worden gemarkeerd met
[ExtensionMarkerName("...")], wat verwijst naar de naam van het type extensiemarkering dat overeenkomt met het extensieblok voor dat element.
Implementatiemethoden
De methodeteksten voor methode-/eigenschapsdeclaraties in een extensieblok in de bron worden verzonden als statische implementatiemethoden in de statische klasse op het hoogste niveau.
- Een implementatiemethode heeft dezelfde naam als de oorspronkelijke methode.
- Het bevat typeparameters die zijn afgeleid van het extensieblok dat is voorbereid op de typeparameters van de oorspronkelijke methode (inclusief kenmerken).
- Het heeft dezelfde toegankelijkheid en kenmerken als de oorspronkelijke methode.
- Als er een statische methode wordt geïmplementeerd, heeft deze dezelfde parameters en retourtype.
- Als er een instantiemethode wordt geïmplementeerd, heeft deze een vooraf gedefinieerde parameter voor de handtekening van de oorspronkelijke methode. De kenmerken, refness, type en naam van deze parameter zijn afgeleid van de extensieparameter die is gedeclareerd in het relevante extensieblok.
- De parameters in implementatiemethoden verwijzen naar typeparameters die eigendom zijn van de implementatiemethode, in plaats van parameters van een extensieblok.
- Als het oorspronkelijke lid een gewone instantiemethode is, wordt de implementatiemethode gemarkeerd met een
[Extension]kenmerk.
Kenmerk ExtensionMarkerName
Het ExtensionMarkerNameAttribute type is alleen bedoeld voor compilergebruik. Het is niet toegestaan in de bron. De typedeclaratie wordt gesynthetiseerd door de compiler als deze nog niet is opgenomen in de compilatie.
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; }
}
Opmerking: Hoewel sommige kenmerkdoelen zijn opgenomen voor toekomstige bestendigheid (extensie geneste typen, extensievelden, extensiegebeurtenissen), is AttributeTargets.Constructor niet opgenomen omdat extensieconstructors, geen constructeurs zouden zijn.
Voorbeeld
Opmerking: we gebruiken vereenvoudigde namen op basis van inhoud voor het voorbeeld, voor leesbaarheid. Opmerking: omdat C# geen typeparameter opnieuw kan worden gedeclareerd, is de code die metadata vertegenwoordigt geen geldige C#-code.
Hier volgt een voorbeeld waarin wordt geïllustreerd hoe groepering werkt, zonder leden:
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>
}
}
wordt uitgezonden als
[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
}
Hier volgt een voorbeeld van hoe leden worden uitgezonden:
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() { ... }
}
wordt uitgezonden als
[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() { ... }
}
Wanneer extensieleden in de bron worden gebruikt, verzenden we deze als verwijzing naar implementatiemethoden.
Bijvoorbeeld: een aanroep van enumerableOfInt.Method() zou worden verzonden als een statische aanroep naar IEnumerableExtensions.Method<int>(enumerableOfInt).
XML-documenten
De opmerkingen in de documentatie over het extensieblok zijn gegeven voor het markeringstype (de DocID voor het extensieblok is E.<>E__MarkerContentName_For_ExtensionOfT'1 in het onderstaande voorbeeld).
Ze mogen verwijzen naar de extensieparameter en typeparameters met en <paramref><typeparamref> respectievelijk).
Opmerking: u mag de extensieparameter of de typeparameters (met <param> en <typeparam>) niet documenteren bij een extensielid.
Als twee verlengingsblokken als één markeringstype worden uitgezonden, worden hun documentatieopmerkingen ook samengevoegd.
Hulpprogramma's die XML-documenten verwerken, zijn verantwoordelijk voor het kopiëren van <param> en <typeparam> van het extensieblok naar de extensieleden, indien van toepassing (de parameterinformatie mag alleen worden gekopieerd naar instantieleden).
Een <inheritdoc> wordt verzonden op implementatiemethoden en verwijst naar het relevante uitbreidingslid met een cref. Bijvoorbeeld, de implementatiemethode voor een getter verwijst naar de documentatie van de extensie-eigenschap.
Als het uitbreidingslid geen documentatiecommentaar heeft, wordt het <inheritdoc> weggelaten.
Voor extensieblokken en extensieleden wordt momenteel niet gewaarschuwd als:
- de extensieparameter wordt gedocumenteerd, maar de parameters op het uitbreidingslid zijn niet
- of omgekeerd
- of in de equivalente scenario's met niet-gedocumenteerde typeparameters
Bijvoorbeeld de volgende opmerkingen bij documenten:
/// <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;
}
}
levert de volgende XML op:
<?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>
CREF-verwijzingen
We kunnen extensieblokken behandelen zoals geneste typen, die kunnen worden geadresseerd door hun handtekening (alsof het een methode met één extensieparameter is).
Voorbeeld: E.extension(ref int).M().
Maar een cref kan geen extensieblok zelf aanpakken.
E.extension(int) kan verwijzen naar een methode met de naam 'extensie' in het type 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
}
}
De opzoeking weet te zoeken in alle overeenkomende extensieblokken.
Omdat we ongedefinieerde verwijzingen naar extensieleden niet toestaan, zou cref ze daarom ook niet toestaan.
De syntaxis zou zijn:
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
;
Het is een fout om extension_member_cref op topniveau (extension(int).M) of genest in een andere extensie (E.extension(int).extension(string).M) te gebruiken.
Brekende wijzigingen
Typen en aliassen hebben mogelijk geen naam 'extensie'.
Openstaande problemen
Tijdelijke sectie van het document met betrekking tot openstaande problemen, waaronder bespreking van niet-gedefinieerde syntaxis en alternatieve ontwerpen
- Moeten we de vereisten van de ontvanger aanpassen bij het toegang krijgen tot een uitbreidingslid? (opmerking)
-
Bevestig(antwoord:extensionvs.extensionsals het trefwoordextension, LDM 2025-03-24) -
Bevestig dat we willen(antwoord: ja, weigeren, LDM 2025-06-11)[ModuleInitializer]weigeren -
Bevestig dat het geen probleem is om extensieblokken te verwijderen als kandidaat voor toegangspunten(antwoord: ja, negeren, LDM 2025-06-11) -
Bevestig langVer-logica (sla nieuwe extensies over en overweeg en rapporteer ze wanneer u deze kiest)(antwoord: bind voorwaardelijke en rapporteer LangVer-fout, met uitzondering van exemplaaruitbreidingsmethoden, LDM 2025-06-11) Moet(antwoord: documentopmerkingen worden op de achtergrond samengevoegd wanneer blokken worden samengevoegd, nietpartialvereist zijn voor uitbreidingsblokken die worden samengevoegd en waarvan de documentatieopmerkingen worden samengevoegd?partialnodig, bevestigd door e-mail 2025-09-03)Controleer of leden niet mogen worden vernoemd naar de omsluitende of uitgebreide typen.(antwoord: ja, bevestigd via e-mail 2025-09-03)
Ga opnieuw naar groepeer-/conflictregels in het licht van het probleem met de draagbaarheid: https://github.com/dotnet/roslyn/issues/79043
(antwoord: dit scenario is opgelost als onderdeel van het nieuwe metagegevensontwerp met namen van inhoudstypen, dit is toegestaan)
De huidige logica bestaat uit het groeperen van extensieblokken met hetzelfde type ontvanger. Dit houdt geen rekening met beperkingen. Dit veroorzaakt een overdrachtsprobleem met dit scenario:
static class E
{
extension<T>(ref T) where T : struct
void M()
extension<T>(T) where T : class
void M()
}
Het voorstel is om dezelfde groeperingslogica te gebruiken die we van plan zijn te gebruiken voor het ontwerp van de uitbreidingsgroeptype, namelijk om rekening te houden met beperkingen op CLR-niveau (d.w.z. negeren van notnull, tuplenamen, nullability-annotaties).
Moet refness worden gecodeerd in de naam van het groeperingstype?
-
Bekijk het voorstel dat(antwoord: bevestigd door e-mail 2025-09-03)refniet is opgenomen in de naam van het extensiegroeperingstype (moet verder worden besproken nadat WG opnieuw groepeer-/conflictregels heeft bekeken, LDM 2025-06-23)
public static class E
{
extension(ref int)
{
public static void M()
}
}
Deze wordt verzonden als:
public static class E
{
public static class <>ExtensionTypeXYZ
{
.. marker method ...
void M()
}
}
nl-NL: En derde-partij CREF-referentie E.extension(ref int).M wordt geëmitteerd als M:E.<>ExtensionGroupingTypeXYZ.M() als ref wordt verwijderd of toegevoegd aan een extensieparameter, willen we waarschijnlijk niet dat de CREF wordt verbroken.
Dit scenario is niet belangrijk, omdat elk gebruik als uitbreiding dubbelzinnigheid zou zijn:
public static class E
{
extension(ref int)
static void M()
extension(int)
static void M()
}
Maar we maken ons zorgen over dit scenario (voor draagbaarheid en bruikbaarheid) en dit moet werken met het voorgestelde metagegevensontwerp nadat we conflictregels hebben aangepast:
static class E
{
extension<T>(ref T) where T : struct
void M()
extension<T>(T) where T : class
void M()
}
Bij het niet in acht nemen van refness is er een nadeel, aangezien we in dit scenario de draagbaarheid verliezen:
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
naam van
Moeten we uitbreidingseigenschappen verbieden in de nameof-functie zoals we dat doen met klassieke en nieuwe uitbreidingsmethoden?(antwoord: we willen 'nameof(EnclosingStaticClass.ExtensionMember) gebruiken. Ontwerp nodig, waarschijnlijk uitstellen tot .NET 10. LDM 2025-06-11)
op patronen gebaseerde constructies
Methoden
Waar moeten nieuwe uitbreidingsmethoden worden gebruikt?(antwoord: dezelfde plaatsen waar klassieke uitbreidingsmethoden in het spel komen, LDM 2025-05-05)
Dit omvat:
-
GetEnumerator/GetAsyncEnumeratorinforeach -
Deconstructbij deconstructie, in positionele patronen en foreach -
Addin verzamelingsinitialisatoren -
GetPinnableReferenceinfixed -
GetAwaiterinawait
Dit geldt niet voor:
-
Dispose/DisposeAsyncinusingenforeach -
MoveNext/MoveNextAsyncinforeach -
Sliceenintindexers in impliciete indexers (en mogelijk lijstpatronen?) -
GetResultinawait
Eigenschappen en indexeerfuncties
Waar horen extensie-eigenschappen en indexeerders een rol te spelen?(antwoord: laten we beginnen met de vier LDM 2025-05-05)
Dit zijn onder andere:
- Voorwerp initializer:
new C() { ExtensionProperty = ... } - woordenlijst initializer:
new C() { [0] = ... } -
with:x with { ExtensionProperty = ... } - eigenschapspatronen:
x is { ExtensionProperty: ... }
We sluiten het volgende uit:
-
Currentinforeach -
IsCompletedinawait -
Count/Lengtheigenschappen en indexeerfuncties in lijstpatroon -
Count/Lengtheigenschappen en indexeerfuncties in impliciete indexeerfuncties
Eigenschappen die delegates teruggeven
Controleer of de extensie-eigenschappen van deze vorm alleen van toepassing moeten zijn in LINQ-query's, zodat ze overeenkomen met de eigenschappen van instanties.(antwoord: logisch, LDM 2025-04-06)
Patroonlijst en verspreidingspatroon
- Controleer of extensie-indexeerfuncties
Index/Rangemoeten worden afgespeeld in lijstpatronen (antwoord: niet relevant voor C# 14)
Kijk opnieuw naar de plaats waar Count/Length extensie-eigenschappen in werking treden
verzamelingsuitdrukkingen
- Extensie
Addwerkt - Extensie
GetEnumeratorwerkt voor verspreiding - De extensie
GetEnumeratorheeft geen invloed op de bepaling van het elementtype (moet een instantie zijn) - Statische
Createextensiemethoden zouden niet als een aangewezen aanmaakmethode moeten tellen - Moeten tellende eigenschappen van uitbreidingen invloed hebben op verzamelingsexpressies?
params verzamelingen
- Extensies
Addhebben geen invloed op de typen die zijn toegestaan metparams
woordenlijstexpressies
- Bevestig dat extensie-indexeerders geen rol spelen in woordenboekexpressies, omdat de aanwezigheid van de indexeerder een integraal onderdeel is van het definiëren van een woordenboektype. (antwoord: niet relevant voor C# 14)
extern
-
we zijn van plan om overdraagbaarheid mogelijk te maken(antwoord: goedgekeurd, LDM 2025-06-23)extern: https://github.com/dotnet/roslyn/issues/78572
Naamgevings-/nummeringsschema voor extensietype
Kwestie
Het huidige nummeringssysteem veroorzaakt problemen met de validatie van openbare API's, die controleert of openbare API's overeenkomen tussen alleen-verwijzingsassembly's en implementatieassembly's.
Moeten we een van de volgende wijzigingen aanbrengen? (antwoord: we gebruiken een naamgevingsschema op basis van inhoud om de stabiliteit van de openbare API te vergroten en de hulpprogramma's moeten nog steeds worden bijgewerkt om rekening te houden met markeringsmethoden)
- het hulpmiddel aanpassen
- gebruik een op inhoud gebaseerd naamgevingsschema (TBD)
- laat de naam worden beheerd via een bepaalde syntaxis
Nieuwe algemene extensie Cast-methode kan nog steeds niet werken in LINQ
Kwestie
In eerdere ontwerpen van rollen/extensies was het mogelijk om alleen de typeargumenten van de methode expliciet op te geven.
Maar nu we ons richten op een schijnloze overgang van klassieke extensiemethoden, moeten alle typeargumenten expliciet worden gegeven.
Dit faalt in het aanpakken van een probleem met het gebruik van de Cast-methode-extensie in LINQ.
Moeten we een wijziging aanbrengen in de uitbreidingsfunctie om dit scenario mogelijk te maken? (antwoord: nee, dit leidt er niet toe dat we teruggaan naar het ontwerp voor extensieresolutie, LDM 2025-05-05)
De extensieparameter beperken voor een extensielid
Moeten we het volgende toestaan? (antwoord: nee, dit kan later worden toegevoegd)
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> { }
Nullbaarheid
-
Bevestig het huidige ontwerp, bijvoorbeeld maximale portabiliteit/compatibiliteit(antwoord: ja, LDM 2025-04-17)
extension([System.Diagnostics.CodeAnalysis.DoesNotReturnIf(false)] bool b)
{
public void AssertTrue() => throw null!;
}
extension([System.Diagnostics.CodeAnalysis.NotNullIfNotNull("o")] ref int? i)
{
public void M(object? o) => throw null!;
}
Metagegevens
Moeten skeletmethoden(antwoord: ja, LDM 2025-04-17)NotSupportedExceptionof een andere standaarduitzondering werpen (doen we dat op dit momentthrow null;)?Moeten we meer dan één parameter accepteren in de markeringsmethode in metagegevens (voor het geval er nieuwe versies meer informatie toevoegen)?(antwoord: we kunnen strikt blijven, LDM 2025-04-17)Moet de uitbreidingsmarkering of spreekbare implementatiemethoden worden gemarkeerd met een speciale naam?(antwoord: de markeringsmethode moet worden gemarkeerd met speciale naam en we moeten deze controleren, maar niet implementatiemethoden, LDM 2025-04-17)Moeten we een kenmerk toevoegen(antwoord: ja, LDM 2025-03-10)[Extension]aan de statische klasse, zelfs als er geen exemplaarextensiemethode is?Controleer of we ook een kenmerk moeten toevoegen(antwoord: nee, LDM 2025-03-10)[Extension]aan implementatie getters en setters.-
Controleer of de extensietypen moeten worden gemarkeerd met een speciale naam en de compiler vereist deze vlag in metagegevens (dit is een belangrijke wijziging van preview)(antwoord: goedgekeurd, LDM 2025-06-23)
scenario voor statische fabriek
Wat zijn de conflictregels voor statische methoden?(antwoord: gebruik bestaande C#-regels voor het omhullende statische type, geen versoepeling, LDM 2025-03-17)
Opzoeken
Hoe kan ik instantiemethode-aanroepen oplossen nu we sprekende implementatienamen hebben?We geven de voorkeur aan de skeletmethode aan de bijbehorende implementatiemethode.Hoe kunt u statische extensiemethoden oplossen?(antwoord: net als methoden voor exemplaaruitbreiding, LDM 2025-03-03)Eigenschappen oplossen(beantwoord in grote lijnen LDM 2025-03-03, maar moet worden opgevolgd voor verbeteringen)-
Bereik- en schaduwregels voor extensieparameter en typeparameters(antwoord: in bereik van extensieblok, schaduw niet toegestaan, LDM 2025-03-10) Hoe moet ORPA van toepassing zijn op nieuwe extensiemethoden?(antwoord: extensieblokken als transparant behandelen, het "bevattende type" voor ORPA is de omsluitende statische klasse, LDM 2025-04-17)
public static class Extensions
{
extension(Type1)
{
[OverloadResolutionPriority(1)]
public void Overload(...)
}
extension(Type2)
{
public void Overload(...)
}
}
Moet ORPA van toepassing zijn op nieuwe extensie-eigenschappen?(antwoord: Ja en ORPA moeten worden gekopieerd in implementatiemethoden, LDM 2025-04-23)
public static class Extensions
{
extension(int[] i)
{
public P { get => }
}
extension(ReadOnlySpan<int> r)
{
[OverloadResolutionPriority(1)]
public P { get => }
}
}
- Hoe de klassieke regels voor extensieoplossing te herzien? Doen we dat?
- de standaard voor klassieke extensiemethoden bijwerken en deze gebruiken om ook nieuwe extensiemethoden te beschrijven,
- behoud de bestaande taal voor klassieke extensiemethoden, gebruik deze om ook nieuwe extensiemethoden te beschrijven, maar een bekende specificatiedeviatie voor beide hebben,
- de bestaande taal voor klassieke extensiemethoden behouden, maar andere taal gebruiken voor nieuwe extensiemethoden en alleen een bekende specificatiedeviatie hebben voor klassieke extensiemethoden?
-
Bevestig dat we expliciete typeargumenten voor een eigenschapstoegang willen weigeren(antwoord: geen eigenschapstoegang met expliciete typeargumenten, besproken in WG)
string s = "ran";
_ = s.P<object>; // error
static class E
{
extension<T>(T t)
{
public int P => 0;
}
}
-
Bevestigen dat we willen dat verbeterregels van toepassing zijn, zelfs wanneer de ontvanger een type is(antwoord: een alleen-type extensieparameter moet worden overwogen bij het oplossen van statische extensieleden, 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;
}
}
-
Bevestig dat we akkoord gaan met het hebben van dubbelzinnigheid wanneer zowel methoden als eigenschappen van toepassing zijn(antwoord: we moeten een voorstel ontwerpen om beter te doen dan de status quo, met het uitstellen naar .NET 10, LDM 2025-06-23) - Bevestig dat we geen verbeteringen voor alle leden willen voordat we het winnende lidtype bepalen (antwoord: punting out of .NET 10, WG 2025-07-02)
string s = null;
s.M(); // error
static class E
{
extension(string s)
{
public System.Action M => throw null;
}
extension(object o)
{
public string M() => throw null;
}
}
Hebben we een impliciete ontvanger binnen uitbreidingsdeclaraties?(antwoord: nee, eerder besproken in LDM)
static class E
{
extension(object o)
{
public void M()
{
M2();
}
public void M2() { }
}
}
Moeten we zoeken op typeparameter toestaan?(discussie) (antwoord: nee, we wachten op feedback, LDM 2025-04-16)
Toegankelijkheid
Wat is de betekenis van toegankelijkheid binnen een uitbreidingsdeclaratie?(antwoord: uitbreidingsdeclaraties tellen niet als een toegankelijkheidsbereik, LDM 2025-03-17)Moeten we de controle 'inconsistente toegankelijkheid' toepassen op de ontvangerparameter, zelfs voor statische leden?(antwoord: ja, LDM 2025-04-17)
public static class Extensions
{
extension(PrivateType p)
{
// We report inconsistent accessibility error,
// because we generate a `public static void M(PrivateType p)` implementation in enclosing type
public void M() { }
public static void M2() { } // should we also report here, even though not technically necessary?
}
private class PrivateType { }
}
Validatie van uitbreidingsdeclaratie
Moeten we de parametervalidatie van het type versoepelen (uitstelbaarheid: alle typeparameters moeten worden weergegeven in het type van de extensieparameter) waar alleen methoden zijn?(antwoord: ja, LDM 2025-04-06) Hierdoor kunnen 100% van klassieke extensiemethoden worden overgezet.
Als jeTResult M<TResult, TSource>(this TSource source)hebt, kun je het overzetten alsextension<TResult, TSource>(TSource source) { TResult M() ... }.Bevestig of alleen-initialisatie-accessors mogen worden toegestaan in extensies(antwoord: in orde om voorlopig niet toe te staan, LDM 2025-04-17)Mag het enige verschil in de ref-ness van de ontvanger worden toegestaan(antwoord: nee, houd de specificatieregel, LDM 2025-03-24)extension(int receiver) { public void M2() {} }extension(ref int receiver) { public void M2() {} }?Moeten we klagen over een conflict als dit(antwoord: ja, regel met specificatie behouden, LDM 2025-03-24)extension(object receiver) { public int P1 => 1; }extension(object receiver) { public int P1 {set{}} }?Moeten we klagen over conflicten tussen skeletmethoden die geen conflicten zijn tussen implementatiemethoden?(antwoord: ja, regel met specificatie behouden, LDM 2025-03-24)
static class E
{
extension(object)
{
public void Method() { }
public static void Method() { }
}
}
De huidige conflictregels zijn: 1. controleer op geen conflict binnen vergelijkbare extensies met behulp van klasse-/structuurregels, 2. Controleer of er geen conflicten zijn tussen implementatiemethoden in verschillende uitbreidingsdeclaraties.
Hebben we het eerste deel van de regels nog nodig?(antwoord: Ja, we houden deze structuur omdat het helpt bij het verbruik van de API's, LDM 2025-03-24)
XML-documenten
Wordt(antwoord: ja, paramref naar extensieparameter is toegestaan bij extensieleden, LDM 2025-05-05)paramrefde ontvangerparameter ondersteund voor extensieleden? Zelfs bij statisch beeld? Hoe wordt deze gecodeerd in de uitvoer? Waarschijnlijk werkt de standaardmethode<paramref name="..."/>voor een mens, maar er bestaat een risico dat sommige bestaande hulpprogramma's deze niet kunnen vinden onder de parameters in de API.Moeten we documentopmerkingen kopiëren naar de implementatiemethoden met spreekbare namen?(antwoord: geen kopie, LDM 2025-05-05)Moet(antwoord: geen kopie, LDM 2025-05-05)<param>het element dat overeenkomt met de ontvanger-parameter worden gekopieerd uit de extensiecontainer voor instantiemethoden? Zijn er nog andere zaken die van de container naar de implementatiemethoden (<typeparam>etc.) moeten worden gekopieerd?Moet(antwoord: nee, voorlopig LDM 2025-05-05)<param>voor de extensieparameter worden toegestaan bij extensieleden als overschrijving?- Wordt de samenvatting van extensieblokken overal weergegeven?
CREF
-
Bevestig de syntaxis(antwoord: voorstel is goed, LDM 2025-06-09) Moet het mogelijk zijn om naar een extensieblok ((antwoord: nee, LDM 2025-06-09)E.extension(int))te verwijzen?Moet het mogelijk zijn om naar een lid te verwijzen met behulp van een niet-gekwalificeerde syntaxis:(antwoord: ja, LDM 2025-06-09)extension(int).Member?- Moeten we verschillende tekens gebruiken voor een onuitsprekelijke naam, om XML-escaping te vermijden? (antwoord: overlaten aan WG, LDM 2025-06-09)
Controleer of zowel verwijzingen naar skelet- als implementatiemethoden mogelijk zijn:(antwoord: ja, LDM 2025-06-09)E.Mvs.E.extension(int).MBeide lijken noodzakelijk (uitbreidingseigenschappen en draagbaarheid van klassieke extensiemethoden).Zijn namen van extensiemetagegevens problematisch voor versiebeheerdocumenten?(antwoord: Ja, we gaan weg van de ordinalen en gebruiken een op inhoud gebaseerd stabiel naamgevingsschema)
Voeg ondersteuning toe voor meer lidtypen.
We hoeven dit ontwerp niet in één keer te implementeren, maar kunnen het een voor een of enkele categorieën tegelijk implementeren. Op basis van bekende scenario's in onze kernbibliotheken moeten we in de volgende volgorde werken:
- Eigenschappen en methoden (instantie en statisch)
- Bedieners
- Indexeerfuncties (exemplaar en statisch, kunnen opportunistisch worden uitgevoerd op een eerder punt)
- Iets anders
Hoeveel willen we vooruitlopen met het ontwerp voor andere soorten leden?
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
;
Geneste typen
Als we ervoor kiezen om verder te gaan met geneste typen extensies, volgen hier enkele opmerkingen uit eerdere discussies:
- Er zou een conflict zijn als twee extensiedeclaraties geneste extensietypen met dezelfde namen en ariteit declareren. We hebben geen oplossing voor het weergeven van deze gegevens in metagegevens.
- De ruwe benadering die we hebben besproken voor metagegevens:
- we zouden een genest skelettype met oorspronkelijke typeparameters en zonder leden uitgeven
- we zouden een geneste implementatietype verzenden met vooraf gedefinieerde typeparameters uit de uitbreidingsdeclaratie en alle lidimplementaties zoals ze in de bron worden weergegeven (moduloverwijzingen naar typeparameters)
Constructeurs
Constructors worden over het algemeen beschreven als een instantiëringslid in C#, omdat het lichaam van de constructor toegang heeft tot de zojuist gemaakte waarde via het this trefwoord.
Dit werkt echter niet goed voor de op parameters gebaseerde benadering van exemplaaruitbreidingsleden, omdat er geen voorafgaande waarde is om als parameter door te geven.
In plaats daarvan lijken extensieconstructors meer op statische fabrieksmethoden.
Ze worden beschouwd als statische leden in de zin dat ze niet afhankelijk zijn van de naam van een ontvangerparameter.
Hun lichamen moeten expliciet het bouwresultaat creëren en retourneren.
Het lid zelf wordt nog steeds gedeclareerd met constructorsyntaxis, maar kan geen initialisaties hebben thisbase en is niet afhankelijk van het ontvangertype met toegankelijke constructors.
Dit betekent ook dat uitbreidingsconstructors kunnen worden gedefinieerd voor typen die geen eigen constructors hebben, zoals interfaces en opsommingstypen.
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) { ... }
}
Toestaan:
var range = new IEnumerable<int>(1, 100);
Kortere formulieren
Het voorgestelde ontwerp vermijdt herhaling van ontvangerspecificaties per lid, maar uiteindelijk worden extensieleden dubbel genest in een statische klasse en uitbreidingsdeclaratie. Het is waarschijnlijk gebruikelijk dat statische klassen slechts één uitbreidingsdeclaratie bevatten of dat uitbreidingsdeclaraties slechts één lid bevatten, en het lijkt ons waarschijnlijk om syntactische afkorting van deze gevallen toe te staan.
Declaraties van statische klassen en extensies samenvoegen:
public static class EmptyExtensions : extension(IEnumerable source)
{
public bool IsEmpty => !source.GetEnumerator().MoveNext();
}
Dit ziet er uiteindelijk meer uit als wat we een 'typegebaseerde' benadering noemen, waarbij de container voor extensieleden zelf de naam heeft.
Uitbreidingsdeclaratie en uitbreidingslid samenvoegen:
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) { ... }
}
Dit lijkt meer op wat we een 'op leden gebaseerde' benadering noemen, waarbij elk uitbreidingslid een eigen ontvangerspecificatie bevat.
C# feature specifications