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.
Notitie
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 Language Design Meeting (LDM-notities).
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/8652
Samenvatting
Verzamelingsexpressies introduceren een nieuwe terse-syntaxis, [e1, e2, e3, etc]
, om algemene verzamelingswaarden te maken. Het inlijnen van andere verzamelingen in deze waarden is mogelijk met behulp van een verspreid element ..e
als volgt: [e1, ..c2, e2, ..c2]
.
Er kunnen verschillende typen die op verzamelingen lijken worden gemaakt zonder externe BCL-ondersteuning te vereisen. Dit zijn de volgende typen:
-
matrixtypen, zoals
int[]
. -
Span<T>
enReadOnlySpan<T>
. - Typen die ondersteuning bieden voor verzamelingsinitializers, zoals
List<T>
.
Verdere ondersteuning is aanwezig voor verzamelingsachtige typen die niet onder het bovenstaande vallen via een nieuw kenmerk en API-patroon dat rechtstreeks op het type zelf kan worden gebruikt.
Motivatie
Verzamelingsachtige waarden zijn enorm aanwezig in programmeren, algoritmen en vooral in het C#/.NET-ecosysteem. Bijna alle programma's gebruiken deze waarden om gegevens op te slaan en gegevens van andere onderdelen te verzenden of te ontvangen. Op dit moment moeten bijna alle C#-programma's veel verschillende en helaas uitgebreide benaderingen gebruiken om exemplaren van dergelijke waarden te maken. Sommige benaderingen hebben ook prestatienadelen. Hier volgen enkele veelvoorkomende voorbeelden:
- Matrices, waarvoor
new Type[]
ofnew[]
vóór de{ ... }
waarden zijn vereist. - Spans, die mogelijk gebruikmaken van
stackalloc
en andere complexe constructies. - Initialisatoren voor verzamelingen, waarvoor een syntaxis zoals
new List<T>
vereist is (met het ontbreken van een mogelijk uitgebreideT
) vóór hun waarden, en die meerdere herallocaties van geheugen kunnen veroorzaken, omdat ze N.Add
-aanroepen gebruiken zonder een initiële capaciteit op te geven. - Onveranderbare verzamelingen, waarvoor syntaxis zoals
ImmutableArray.Create(...)
nodig is om de waarden te initialiseren en die intermediaire toewijzingen en het kopiëren van gegevens kunnen veroorzaken. Efficiëntere bouwvormen (zoalsImmutableArray.CreateBuilder
) zijn onhandig en produceren nog steeds onvermijdelijk afval.
- Matrices, waarvoor
Als we kijken naar het omringende ecosysteem, vinden we ook overal voorbeelden van het maken van lijsten die handiger en prettiger zijn om te gebruiken. TypeScript, Dart, Swift, Elm, Python en meer kiezen voor een beknopte syntaxis voor dit doel, met wijdverspreid gebruik en tot een groot effect. Oppervlakkige onderzoeken hebben geen wezenlijke problemen onthuld die in deze ecosystemen ontstaan door ingebouwde literals.
C# heeft ook lijstpatronen toegevoegd in C# 11. Met dit patroon kunnen lijstachtige waarden overeenkomen en worden gedeconstrueerd met een heldere en intuïtieve syntaxis. In tegenstelling tot bijna alle andere patroonconstructies, ontbreekt het deze overeenkomende/deconstructiesyntaxis echter aan de bijbehorende constructiesyntaxis.
Het verkrijgen van de beste prestaties voor het maken van elk verzamelingstype kan lastig zijn. Eenvoudige oplossingen verspillen vaak zowel CPU als geheugen. Het gebruik van een literal biedt maximale flexibiliteit voor de implementatie van de compiler om de literal te optimaliseren en op zijn minst een even goed resultaat te bereiken als een gebruiker zou kunnen leveren, maar met eenvoudige code. Zeer vaak zal de compiler in staat zijn om beter te doen, en de specificatie is erop gericht om de implementatie grote hoeveelheden ruimte te bieden in termen van implementatiestrategie om dit te garanderen.
Er is een inclusieve oplossing nodig voor C#. Het moet voldoen aan de overgrote meerderheid van cases voor klanten in termen van de verzamelachtige typen en waarden die ze al hebben. Het moet ook natuurlijk aanvoelen in de taal en het werk weerspiegelen dat is uitgevoerd in patroonherkenning.
Dit leidt tot een natuurlijke conclusie dat de syntaxis moet lijken op [e1, e2, e3, e-etc]
of [e1, ..c2, e2]
, die overeenkomen met de patroonequivalenten van [p1, p2, p3, p-etc]
en [p1, ..p2, p3]
.
Gedetailleerd ontwerp
De volgende grammaticaproducties worden toegevoegd:
primary_no_array_creation_expression
...
+ | collection_expression
;
+ collection_expression
: '[' ']'
| '[' collection_element ( ',' collection_element )* ']'
;
+ collection_element
: expression_element
| spread_element
;
+ expression_element
: expression
;
+ spread_element
: '..' expression
;
Verzamelingletterlijkheden zijn doelgericht getypeerd.
Specificatie-verduidelijkingen
Voor kortheid wordt
collection_expression
in de volgende secties 'letterlijk' genoemd.expression_element
exemplaren worden meestal aangeduid alse1
,e_n
, enzovoort.spread_element
exemplaren worden meestal aangeduid als..s1
,..s_n
, enzovoort.spansoort betekent
Span<T>
ofReadOnlySpan<T>
.Letterlijke gegevens worden meestal weergegeven als
[e1, ..s1, e2, ..s2, etc]
om een willekeurig aantal elementen in elke volgorde over te brengen. Belangrijk is dat dit formulier wordt gebruikt om alle gevallen weer te geven, zoals:- Lege letterlijke waarden
[]
- Letterlijke gegevens zonder
expression_element
erin. - Letterlijke gegevens zonder
spread_element
erin. - Letterlijke waarden met willekeurige volgorde van ieder elementtype.
- Lege letterlijke waarden
Het iteratietype van
..s_n
is het type van de iteratievariabele, bepaald alsofs_n
werden gebruikt als de expressie waarover wordt geïtereerd in eenforeach_statement
.Variabelen die beginnen met
__name
worden gebruikt om de resultaten van de evaluatie vanname
weer te geven, opgeslagen op een locatie, zodat deze slechts eenmaal worden geëvalueerd.__e1
is bijvoorbeeld de evaluatie vane1
.List<T>
,IEnumerable<T>
, enzovoort verwijzen naar de respectieve typen in deSystem.Collections.Generic
naamruimte.In de specificatie wordt een -vertaling van het literaal naar de bestaande C#-constructies gedefinieerd. Net als bij de query-expressievertaling, is de letterlijke tekst alleen geldig als de vertaling tot geldige code zou leiden. Het doel van deze regel is om te voorkomen dat u andere regels van de taal moet herhalen die worden geïmpliceerd (bijvoorbeeld over de conversiebaarheid van expressies wanneer deze worden toegewezen aan opslaglocaties).
Een implementatie is niet vereist om letterlijke gegevens exact te vertalen zoals hieronder is opgegeven. Elke vertaling is legaal als hetzelfde resultaat wordt geproduceerd en er geen waarneembare verschillen zijn in de productie van het resultaat.
- Een implementatie kan bijvoorbeeld letterlijke waarden, zoals
[1, 2, 3]
, rechtstreeks vertalen naar eennew int[] { 1, 2, 3 }
-expressie dat de onbewerkte gegevens in de assembly opneemt, waardoor de noodzaak van__index
of een reeks instructies om elke waarde toe te wijzen, wordt geëlimineerd. Belangrijk is dat dit betekent dat als een stap van de vertaling tijdens runtime een uitzondering kan veroorzaken dat de status van het programma nog steeds in de staat blijft die wordt aangegeven door de vertaling.
- Een implementatie kan bijvoorbeeld letterlijke waarden, zoals
Verwijzingen naar 'stack allocation' betreffen elke strategie om op de stack en niet op de heap te alloceren. Belangrijk is dat deze strategie niet impliceert of vereist dat deze strategie wordt uitgevoerd via het daadwerkelijke
stackalloc
mechanisme. Het gebruik van inlinematrices is bijvoorbeeld ook een toegestane en wenselijke benadering om stacktoewijzing waar beschikbaar te realiseren. In C# 12 kunnen inlinematrices niet worden geïnitialiseerd met een verzamelingsexpressie. Dat blijft een open voorstel.Verzamelingen worden als goed oplosbaar beschouwd. Bijvoorbeeld:
- Er wordt van uitgegaan dat de waarde van
Count
in een verzameling dezelfde waarde produceert als het aantal elementen dat wordt opgesomd. - De typen die worden gebruikt in deze specificatie die zijn gedefinieerd in de
System.Collections.Generic
naamruimte, worden geacht neveneffectvrij te zijn. Als zodanig kan de compiler scenario's optimaliseren waarin dergelijke typen kunnen worden gebruikt als intermediaire waarden, maar anders niet beschikbaar worden gesteld. - Er wordt aangenomen dat een aanroep van een toepasselijk
.AddRange(x)
-lid op een verzameling resulteert in dezelfde uiteindelijke waarde als het itereren overx
en het afzonderlijk opsommen van al zijn waarden aan de verzameling met.Add
. - Het gedrag van verzamelingsliterals met verzamelingen die zich niet goed gedragen, is niet gedefinieerd.
- Er wordt van uitgegaan dat de waarde van
Conversies
Met een verzamelingsexpressieconversie kan een verzamelingsexpressie naar een type worden geconverteerd.
Een impliciete conversie van verzamelingsexpressie bestaat vanuit een verzamelingsexpressie naar de volgende typen:
- Een enkeldimensionaal matrixtype
T[]
, in welk geval het elementtype isT
- Een span type:
System.Span<T>
System.ReadOnlySpan<T>
In welk geval het elementtype isT
- Een type met een geschikte aanmaakmethode, in welk geval het elementtype het iteratietype is, bepaald door een
GetEnumerator
instantiemethode of een opsommingsinterface, niet door een extensiemethode. - Een struct of klasse type dat
System.Collections.IEnumerable
implementeert, waarbij:Het type heeft een toepasselijke constructor die zonder argumenten kan worden aangeroepen en de constructor is toegankelijk op de locatie van de verzamelingsexpressie.
Als de verzamelingsexpressie elementen bevat, heeft het type een instantie- of extensiemethode
Add
waar:- De methode kan worden aangeroepen met één waardeargument.
- Als de methode algemeen is, kunnen de typeargumenten worden afgeleid uit de verzameling en het argument.
- De methode is toegankelijk op de locatie van de verzamelingsexpressie.
In dat geval is het elementtype het iteratietype van het type.
- Een interfacetype:
System.Collections.Generic.IEnumerable<T>
System.Collections.Generic.IReadOnlyCollection<T>
System.Collections.Generic.IReadOnlyList<T>
System.Collections.Generic.ICollection<T>
System.Collections.Generic.IList<T>
In welk geval het elementtype isT
De impliciete conversie bestaat als het type een elementtype heeftT
waarbij voor elk elementEᵢ
in de verzamelingsexpressie:
- Als
Eᵢ
een expressie-elementis, is er een impliciete conversie vanEᵢ
naarT
. - Als
Eᵢ
een verspreid element is..Sᵢ
, is er een impliciete conversie van het iteratietype vanSᵢ
naarT
.
Er is geen conversie van een verzamelingsexpressie naar een multidimensionale matrixtype.
Typen waarvoor een impliciete verzamelingsexpressieconversie van een verzamelingsexpressie bestaat, zijn de geldige doeltypen voor die verzamelingsexpressie.
De volgende aanvullende impliciete conversies bestaan uit een verzamelingsexpressie:
Naar een null-waarde waardetype
T?
waar een conversie van verzamelingsexpressies van de verzamelingsexpressie naar een waardetype plaatsvindtT
. De conversie is een conversie van verzamelingsexpressies naarT
gevolgd door een impliciete null-conversie vanT
naarT?
.Voor een verwijzingstype
T
waarbij er een aanmaakmethode is gekoppeld aanT
die een typeU
retourneert en een impliciete referentieconversie van naarU
mogelijk maakt. De conversie is een conversie van verzamelingsexpressies naarU
gevolgd door een impliciete verwijzingsconversie vanU
naarT
.Voor een interfacetype
I
waar een create-methode is geassocieerd metI
die een typeV
retourneert en een impliciete boksconversie vanV
naarI
. De conversie is een -omzetting van verzamelingsexpressies naarV
, gevolgd door een impliciete boxing-conversie vanV
naarI
.
Methoden maken
Een aanmaakmethode wordt aangegeven met een [CollectionBuilder(...)]
attribuut op het verzamelingstype.
Het kenmerk specificeert het opbouwtype en de methodenaam van een methode die aangeroepen moet worden om een exemplaar van het type verzameling te maken.
namespace System.Runtime.CompilerServices
{
[AttributeUsage(
AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface,
Inherited = false,
AllowMultiple = false)]
public sealed class CollectionBuilderAttribute : System.Attribute
{
public CollectionBuilderAttribute(Type builderType, string methodName);
public Type BuilderType { get; }
public string MethodName { get; }
}
}
Het kenmerk kan worden toegepast op een class
, struct
, ref struct
of interface
.
Het kenmerk wordt niet overgenomen, hoewel het kenmerk kan worden toegepast op een basis-class
of een abstract class
.
Het opbouwfunctietype moet een niet-generiek class
of struct
zijn.
Eerst wordt de set van toepasselijke aanmaakmethodenCM
bepaald.
Het bestaat uit methoden die voldoen aan de volgende vereisten:
- De methode moet de naam hebben die is opgegeven in het kenmerk
[CollectionBuilder(...)]
. - De methode moet rechtstreeks worden gedefinieerd op het builder-type.
- De methode moet
static
zijn. - De methode moet toegankelijk zijn wanneer de verzamelingsexpressie wordt gebruikt.
- De arity van de methode moet overeenkomen met de arity van het verzamelingstype.
- De methode moet één parameter van het type
System.ReadOnlySpan<E>
hebben, doorgegeven door waarde. - Er is een identiteitsconversie, impliciete verwijzingsconversie, of boksconversie van het methodeteruggavetypenaar het verzamelingstype.
Methoden die zijn gedeclareerd op basistypen of interfaces, worden genegeerd en maken geen deel uit van de CM
set.
Als de CM
set leeg is, dan heeft het verzamelingstype geen elementtype en geen creatiemethode. Geen van de volgende stappen is van toepassing.
Als slechts één methode in de CM
set een identiteitsconversie heeft van E
naar het elementtype van het verzamelingstype, is dit de methode voor maken voor het verzamelingstype. Anders heeft het verzamelingstype geen methode om te creëren.
Er wordt een fout gerapporteerd als het kenmerk [CollectionBuilder]
niet verwijst naar een aanroepbare methode met de verwachte handtekening.
Voor een verzamelingsexpressie met een doeltype C<S0, S1, …>
waarbij de declaratie van het typeC<T0, T1, …>
een gekoppelde opbouwmethodeB.M<U0, U1, …>()
heeft, worden de algemene typeargumenten van het doeltype op volgorde toegepast, van de buitenste naar de binnenste bevattende typen op de opbouwmethode.
De spanparameter voor de create-methode kan expliciet worden gemarkeerd scoped
of [UnscopedRef]
. Als de parameter impliciet of expliciet scoped
is, kan de compiler de opslag voor de span op de stack toewijzen in plaats van de heap.
Een mogelijke voorbeeldmethode maken voor ImmutableArray<T>
:
[CollectionBuilder(typeof(ImmutableArray), "Create")]
public struct ImmutableArray<T> { ... }
public static class ImmutableArray
{
public static ImmutableArray<T> Create<T>(ReadOnlySpan<T> items) { ... }
}
Met de create-methode hierboven, kan ImmutableArray<int> ia = [1, 2, 3];
worden verzonden als:
[InlineArray(3)] struct __InlineArray3<T> { private T _element0; }
Span<int> __tmp = new __InlineArray3<int>();
__tmp[0] = 1;
__tmp[1] = 2;
__tmp[2] = 3;
ImmutableArray<int> ia =
ImmutableArray.Create((ReadOnlySpan<int>)__tmp);
Constructie
De elementen van een verzamelingsexpressie worden geëvalueerd in volgorde van links naar rechts. Elk element wordt precies eenmaal geëvalueerd en eventuele verdere verwijzingen naar de elementen verwijzen naar de resultaten van deze eerste evaluatie.
Een spreidelement kan worden herhaald voordat of nadat de volgende elementen in de verzamelingsuitdrukking worden geëvalueerd.
Een onopgevangen uitzondering die optreedt door een van de methoden die tijdens de constructie worden gebruikt, zal verdere stappen in de constructie verhinderen.
Length
, Count
en GetEnumerator
worden verondersteld geen bijwerkingen te hebben.
Als het doeltype een struct of een klassetype is dat System.Collections.IEnumerable
implementeert, en het doeltype heeft geen creatiemethode, dan is de constructie van het verzamelingsexemplaar als volgt:
De elementen worden op volgorde geëvalueerd. Sommige of alle elementen kunnen tijdens de onderstaande stappen worden geëvalueerd in plaats van eerder.
De compiler kan de bekende lengte van de verzamelingsexpressie bepalen door te tellen eigenschappen ( of gelijkwaardige eigenschappen van bekende interfaces of typen) op elke expressie van verspreide elementenaan te roepen.
De constructor die van toepassing is zonder argumenten, wordt aangeroepen.
Voor elk element in volgorde:
- Als het element een expressie-element is, wordt de toepasselijke
Add
instantie of extensiemethode aangeroepen met het element expressie als het argument. (In tegenstelling tot het klassieke initialisatiegedrag van verzamelingen, zijn elementevaluatie enAdd
-aanroepen niet noodzakelijkerwijs afgewisseld.) - Als het element een verspreid element is wordt een van de volgende opties gebruikt:
- Er wordt een toepasselijke
GetEnumerator
-instantie of extensiemethode aangeroepen op de expressie van het -spread-element, en voor elk item van de enumerator wordt de toepasselijkeAdd
-instantie of extensiemethode aangeroepen op het -verzamelingsinstance met het item als argument. Als de enumeratorIDisposable
implementeert, wordtDispose
na opsomming aangeroepen, ongeacht uitzonderingen. - Er wordt een toepasselijke
AddRange
-instantie of extensiemethode aangeroepen op de -verzamelingsinstantie met het verspreide -expressie als argument. - Een toepasselijke
CopyTo
instantie- of extensiemethode wordt aangeroepen op het spreidingselement van de expressie met het verzamelingsexemplaar enint
index als argumenten.
- Er wordt een toepasselijke
- Als het element een expressie-element is, wordt de toepasselijke
Tijdens de bovenstaande bouwstappen kan een toepasselijke
EnsureCapacity
instantie of uitbreidingsmethode een of meer keren worden aangeroepen op de verzamelingsinstantie met eenint
capaciteitsargument.
Als het doeltype een array, een span, een type met een aanmaakmethode, of een interfaceis, wordt de constructie van het verzamelingsexemplaar als volgt uitgevoerd:
De elementen worden op volgorde geëvalueerd. Sommige of alle elementen kunnen tijdens de onderstaande stappen worden geëvalueerd in plaats van eerder.
De compiler kan de bekende lengte van de verzamelingsexpressie bepalen door te tellen eigenschappen ( of gelijkwaardige eigenschappen van bekende interfaces of typen) op elke expressie van verspreide elementenaan te roepen.
Er wordt als volgt een initialisatie-exemplaar gemaakt:
- Als het doeltype een matrix is en de verzamelingsexpressie een bekende lengteheeft, wordt een matrix toegewezen met de verwachte lengte.
- Als het doeltype een span is of een type met een creatiemethode, en de verzameling een bekende lengteheeft, wordt een span met de verwachte lengte gemaakt dat verwijst naar aaneengesloten opslag.
- Anders wordt tussenliggende opslag toegewezen.
Voor elk element in volgorde:
- Als het element een expressie-elementis, wordt de initialisatie-instantie indexeerfunctie aangeroepen om de geëvalueerde expressie toe te voegen aan de huidige index.
- Als het element een verspreid element is wordt een van de volgende opties gebruikt:
- Een lid van een bekende interface of type wordt aangeroepen om items van de spreidelementexpressie te kopiëren naar de initialisatie-instantie.
- Een toepasselijke
GetEnumerator
instantie- of extensiemethode wordt aangeroepen op de spread-element expressie en voor elk item uit de enumerator wordt de initialisatie-instantie indexer aangeroepen om het item toe te voegen bij de huidige index. Als de enumeratorIDisposable
implementeert, wordtDispose
na opsomming aangeroepen, ongeacht uitzonderingen. - Een toepasselijke
CopyTo
instantie- of extensiemethode wordt aangeroepen op de expressie van het spread-element met de initialisatie-instantie enint
index als argumenten.
Als tussenliggende opslag is toegewezen voor de verzameling, wordt een verzamelingsvoorbeeld toegewezen met de werkelijke lengte van de verzameling en worden de waarden van het initialisatievoorbeeld gekopieerd naar het verzamelingsvoorbeeld, of als een reeks is vereist, kan de compiler een reeks van de werkelijke verzamelingslengte uit de tussenliggende opslag hanteren. Anders is de initialisatie-instantie de verzameling-instantie.
Als het doeltype een maakmethode heeft, wordt de maakmethode gebruikt met het span-exemplaar.
Opmerking: De compiler kan het toevoegen van elementen aan de verzameling vertragen, of het itereren door verspreide elementen uitstellen, totdat de volgende elementen zijn geëvalueerd. (Wanneer volgende verspreide elementen telbare eigenschappen hebben, waardoor de verwachte lengte van de verzameling kan worden berekend voordat de verzameling wordt toegewezen.) Omgekeerd kan de compiler elementen gretig aan de verzameling toevoegen en gretig door verspreide elementen itereren wanneer uitstel geen voordeel biedt.
Houd rekening met de volgende verzamelingsexpressie:
int[] x = [a, ..b, ..c, d];
Als de verspreide elementen
b
enc
telbaar zijn, kan de compiler het toevoegen van items uita
enb
uitstellen totdatc
is geëvalueerd, zodat de resulterende array kan worden gealloceerd op de verwachte lengte. Daarna kon de compiler graag items uitc
toevoegen, voordatd
wordt geëvalueerd.var __tmp1 = a; var __tmp2 = b; var __tmp3 = c; var __result = new int[2 + __tmp2.Length + __tmp3.Length]; int __index = 0; __result[__index++] = __tmp1; foreach (var __i in __tmp2) __result[__index++] = __i; foreach (var __i in __tmp3) __result[__index++] = __i; __result[__index++] = d; x = __result;
Letterlijke verzameling leegmaken
De lege literal
[]
heeft geen type. Net als bij de null-literalkan deze literal echter impliciet worden geconverteerd naar elk constructible verzamelingstype.Het volgende is bijvoorbeeld niet legaal omdat er geen doeltype is en er geen andere conversies betrokken zijn:
var v = []; // illegal
Het is toegestaan een lege letterlijke waarde weg te laten. Bijvoorbeeld:
bool b = ... List<int> l = [x, y, .. b ? [1, 2, 3] : []];
Als
b
onwaar is, is het niet vereist dat een waarde daadwerkelijk wordt samengesteld voor de lege verzamelingsexpressie, omdat deze onmiddellijk zou worden verdeeld in nulwaarden in de uiteindelijke letterlijke waarde.De lege verzamelingsexpressie mag een singleton zijn als deze wordt gebruikt om een uiteindelijke verzamelingswaarde te maken die niet kan worden gedempt. Bijvoorbeeld:
// Can be a singleton, like Array.Empty<int>() int[] x = []; // Can be a singleton. Allowed to use Array.Empty<int>(), Enumerable.Empty<int>(), // or any other implementation that can not be mutated. IEnumerable<int> y = []; // Must not be a singleton. Value must be allowed to mutate, and should not mutate // other references elsewhere. List<int> z = [];
Ref-veiligheid
Zie veilige contextbeperking voor definities van de waarden voor veilige context: declaratieblok, functieliden aanroepercontext.
De veilige context van een verzamelingsexpressie is:
De veilige context van een lege verzamelingsexpressie
[]
is de aanroepercontext.Als het doeltype een spanningssoort
System.ReadOnlySpan<T>
is enT
een van de primitieve typenbool
,sbyte
,byte
,short
,ushort
,char
,int
,uint
,long
,ulong
,float
ofdouble
, en de verzamelingsexpressie bevat alleen constante waarden, is de veilige omgeving van de verzamelingsexpressie de aanroepercontext.Als het doeltype een spantype is
System.Span<T>
ofSystem.ReadOnlySpan<T>
, is de veilige context van de verzamelingsexpressie het declaratieblok.Als het doeltype een ref struct type is met een maakmethode, dan is de veilige context van de expressie voor de verzameling de veilige context van een aanroep van de maakmethode, waarbij de verzamelingsexpressie het bereikargument voor de methode is.
Anders is de veilige context van de verzamelingsexpressie de aanroepercontext.
Een verzamelingsexpressie met een veilige context van declaratieblok kan niet ontsnappen aan het omsluitende bereik, en de compiler mag de verzameling op de stapel opslaan in plaats van in de heap.
Als u een collectie-expressie voor een verwijzingstype buiten het declaratieblokwilt laten komen, kan het nodig zijn om de expressie naar een ander type te casten.
static ReadOnlySpan<int> AsSpanConstants()
{
return [1, 2, 3]; // ok: span refers to assembly data section
}
static ReadOnlySpan<T> AsSpan2<T>(T x, T y)
{
return [x, y]; // error: span may refer to stack data
}
static ReadOnlySpan<T> AsSpan3<T>(T x, T y, T z)
{
return (T[])[x, y, z]; // ok: span refers to T[] on heap
}
Type inferentie
var a = AsArray([1, 2, 3]); // AsArray<int>(int[])
var b = AsListOfArray([[4, 5], []]); // AsListOfArray<int>(List<int[]>)
static T[] AsArray<T>(T[] arg) => arg;
static List<T[]> AsListOfArray<T>(List<T[]> arg) => arg;
De type-inferentie regels worden als volgt bijgewerkt.
De bestaande regels voor de eerste fase worden geëxtraheerd naar een nieuwe invoertypedeductie sectie en er wordt een regel toegevoegd aan invoertypedeductie en uitvoertypedeductie voor expressies voor verzamelingen.
11.6.3.2 De eerste fase
Voor elk van de methodeargumenten
Eᵢ
:
- Een inferentie van invoertype wordt gemaakt van
Eᵢ
naar het overeenkomstige parametertypeTᵢ
.Een invoertypedeductie wordt afgeleid
E
een expressie naar een typeT
op de volgende manier:
- Als
E
een verzamelingsexpressie is met elementenEᵢ
enT
een type is met een elementtypeTₑ
ofT
een nullable waarde type isT0?
enT0
een elementtypeTₑ
heeft, dan geldt voor elkeEᵢ
:
- Als
Eᵢ
een expressie-elementis, wordt een invoertypedeductie gemaakt vanEᵢ
totTₑ
.- Als
Eᵢ
een verspreid element is met een iteratietypeSᵢ
, wordt een ondergrensdeductie gemaakt vanSᵢ
totTₑ
.- [bestaande regels uit de eerste fase] ...
11.6.3.7 Uitvoertypededucties
Een uitvoertype-afleiding wordt gemaakt van een expressie
E
naar een typeT
op de volgende manier:
- Als
E
een verzamelingsexpressie is met elementenEᵢ
enT
een type is met een elementtypeTₑ
ofT
een nullable waarde type isT0?
enT0
een elementtypeTₑ
heeft, dan geldt voor elkeEᵢ
:
- Als
Eᵢ
een expressie-elementis, wordt een uitvoertypedeductie gemaakt vanEᵢ
totTₑ
.- Als
Eᵢ
een verspreid elementis, wordt er geen deductie gemaakt vanEᵢ
.- [bestaande regels van uitvoertypededucties] ...
Extensiemethoden
Er worden geen wijzigingen aangebracht aan de aanroepregels van de uitbreidingsmethode .
12.8.10.3 Uitbreidingsmethode aanroepen
Een extensiemethode
Cᵢ.Mₑ
komt in aanmerking als:
- ...
- Er bestaat een impliciete identiteit, verwijzing of boxing-conversie van expr naar het type van de eerste parameter van
Mₑ
.
Een verzamelingsexpressie heeft geen natuurlijk type, zodat de bestaande conversies van type niet van toepassing zijn. Als gevolg hiervan kan een verzamelingsexpressie niet rechtstreeks worden gebruikt als de eerste parameter voor een aanroep van een extensiemethode.
static class Extensions
{
public static ImmutableArray<T> AsImmutableArray<T>(this ImmutableArray<T> arg) => arg;
}
var x = [1].AsImmutableArray(); // error: collection expression has no target type
var y = [2].AsImmutableArray<int>(); // error: ...
var z = Extensions.AsImmutableArray([3]); // ok
Overbelastingsresolutie
Betere conversie van expressies wordt bijgewerkt om de voorkeur te geven aan bepaalde doeltypen in conversies van verzamelingsexpressies.
In de bijgewerkte regels:
- Een span_type is een van de volgende:
System.Span<T>
-
System.ReadOnlySpan<T>
.
- Een array_or_array_interface is een van de volgende:
- een arraytype
- een van de volgende interfacetypen geïmplementeerd door een matrixtype:
System.Collections.Generic.IEnumerable<T>
System.Collections.Generic.IReadOnlyCollection<T>
System.Collections.Generic.IReadOnlyList<T>
System.Collections.Generic.ICollection<T>
System.Collections.Generic.IList<T>
Gezien een impliciete conversie
C₁
die wordt geconverteerd van een expressieE
naar een typeT₁
en een impliciete conversieC₂
die converteert van een expressieE
naar een typeT₂
, isC₁
een betere conversie danC₂
als een van de volgende bewaringen geldt:
E
is een verzamelingsexpressie en een van de volgende bewaringen:
T₁
isSystem.ReadOnlySpan<E₁>
enT₂
isSystem.Span<E₂>
en er bestaat een impliciete conversie vanE₁
totE₂
T₁
isSystem.ReadOnlySpan<E₁>
ofSystem.Span<E₁>
, enT₂
is een array_or_array_interface met elementtypeE₂
en een impliciete conversie bestaat vanE₁
naarE₂
T₁
is geen span_typeenT₂
is geen span_typeen er bestaat een impliciete conversie vanT₁
totT₂
E
is geen verzamelingsuitdrukking en één van de volgende geldt:
E
komt exact overeen metT₁
enE
komt niet exact overeen metT₂
E
komt exact overeen met beiden of geen vanT₁
enT₂
, enT₁
is een betere conversiedoel danT₂
E
is een methodegroep, ...
Voorbeelden van verschillen met overbelastingsresolutie tussen matrix initializers en verzamelingsexpressies:
static void Generic<T>(Span<T> value) { }
static void Generic<T>(T[] value) { }
static void SpanDerived(Span<string> value) { }
static void SpanDerived(object[] value) { }
static void ArrayDerived(Span<object> value) { }
static void ArrayDerived(string[] value) { }
// Array initializers
Generic(new[] { "" }); // string[]
SpanDerived(new[] { "" }); // ambiguous
ArrayDerived(new[] { "" }); // string[]
// Collection expressions
Generic([""]); // Span<string>
SpanDerived([""]); // Span<string>
ArrayDerived([""]); // ambiguous
Spantypen
De spantypen ReadOnlySpan<T>
en Span<T>
zijn beide constructibele verzamelingstypen. De ondersteuning voor hen volgt het ontwerp voor params Span<T>
. Het samenstellen van een van deze spanten resulteert in een matrix T[] die is gemaakt op de stack als de parametermatrix binnen de limieten (indien van toepassing) is die is ingesteld door de compiler. Anders wordt de array opgeslagen in de heap.
Als de compiler ervoor kiest om op de stack toe te wijzen, is het niet vereist om een literal direct naar een stackalloc
op dat specifieke punt te vertalen. Bijvoorbeeld:
foreach (var x in y)
{
Span<int> span = [a, b, c];
// do things with span
}
De compiler mag dat vertalen met behulp van stackalloc
zolang de Span
betekenis hetzelfde blijft en span-safety wordt gehandhaafd. U kunt het bovenstaande bijvoorbeeld vertalen naar:
Span<int> __buffer = stackalloc int[3];
foreach (var x in y)
{
__buffer[0] = a
__buffer[1] = b
__buffer[2] = c;
Span<int> span = __buffer;
// do things with span
}
De compiler kan ook inline-arrays, indien beschikbaar, gebruiken bij het toewijzen op de stack. In C# 12 kunnen inlinematrices niet worden geïnitialiseerd met een verzamelingsexpressie. Deze functie is een open voorstel.
Als de compiler besluit op de heap te alloceren, is de vertaling voor Span<T>
simpelweg:
T[] __array = [...]; // using existing rules
Span<T> __result = __array;
Letterlijke vertaling van verzameling
Een verzamelingsexpressie heeft een bekende lengte als het compile-tijdtype van elk uitgebreid element in de verzamelingsexpressie telbaar is.
Interfacevertaling
Niet-veranderlijke interfacevertaling
Gezien een doeltype dat geen mutatieleden bevat, namelijk IEnumerable<T>
, IReadOnlyCollection<T>
en IReadOnlyList<T>
, is een compatibele implementatie vereist om een waarde te produceren waarmee die interface wordt geïmplementeerd. Als een type wordt gesynthetiseerd, wordt aanbevolen dat het gesynthetiseerde type al deze interfaces implementeert, evenals ICollection<T>
en IList<T>
, ongeacht welk type interface was getarget. Dit zorgt voor maximale compatibiliteit met bestaande bibliotheken, met inbegrip van bibliotheken die de interfaces die door een waarde zijn geïmplementeerd, introspecteren om prestatieoptimalisaties mogelijk te maken.
Bovendien moet de waarde de niet-gegenereerde ICollection
- en IList
interfaces implementeren. Hierdoor kunnen verzamelingsexpressies dynamische introspectie ondersteunen in scenario's zoals gegevensbinding.
Een compatibele implementatie is gratis voor:
- Gebruik een bestaand type waarmee de vereiste interfaces worden geïmplementeerd.
- Synthetiseer een type dat de vereiste interfaces implementeert.
In beide gevallen mag het gebruikte type een grotere set interfaces implementeren dan strikt vereist.
Gesynthetiseerde typen zijn vrij om elke strategie te gebruiken die ze willen om de vereiste interfaces correct te implementeren. Een gesynthetiseerd type kan bijvoorbeeld de elementen direct binnen zichzelf opnemen, waardoor er geen extra interne toewijzingen voor verzamelingen nodig zijn. Een gesynthetiseerd type kan ook helemaal geen opslag gebruiken en ervoor kiezen om de waarden rechtstreeks te berekenen. Bijvoorbeeld het retourneren van index + 1
voor [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
.
- De waarde moet
true
retourneren wanneer een query wordt uitgevoerd opICollection<T>.IsReadOnly
(indien geïmplementeerd) en niet-gegenereerdeIList.IsReadOnly
enIList.IsFixedSize
. Dit zorgt ervoor dat gebruikers op de juiste wijze kunnen zien dat de verzamelingen niet-veranderlijk zijn, ondanks de implementatie van de veranderlijke zichten. - De waarde moet elke aanroep van een mutatiemethode (zoals
IList<T>.Add
) aanroepen. Dit zorgt voor veiligheid, waardoor een onveranderbare verzameling niet per ongeluk kan worden gewijzigd.
Veranderbare interfacevertaling
Gegeven doeltype dat mutatieleden bevat, namelijk ICollection<T>
of IList<T>
:
- De waarde moet een exemplaar van
List<T>
zijn.
Vertaling van bekende lengte
Een bekende lengte zorgt ervoor dat er efficiënt een resultaat kan worden geconstrueerd zonder dat gegevens hoeven te worden gekopieerd en zonder onnodige extra ruimte in een resultaat.
Het ontbreken van een bekende lengte verhindert niet dat er een resultaat wordt gegenereerd. Het kan echter leiden tot extra CPU- en geheugenkosten die de gegevens produceren en vervolgens naar de uiteindelijke bestemming gaan.
Voor een bekende lengte letterlijke
[e1, ..s1, etc]
begint de vertaling eerst met het volgende:int __len = count_of_expression_elements + __s1.Count; ... __s_n.Count;
Op basis van een doeltype
T
voor die letterlijke gegevens:Als
T
een bepaaldeT1[]
is, wordt de letterlijke vertaling als volgt vertaald:T1[] __result = new T1[__len]; int __index = 0; __result[__index++] = __e1; foreach (T1 __t in __s1) __result[__index++] = __t; // further assignments of the remaining elements
De implementatie mag andere middelen gebruiken om de matrix te vullen. Gebruik bijvoorbeeld efficiënte methoden voor bulksgewijs kopiëren, zoals
.CopyTo()
.Als
T
een aantalSpan<T1>
is, wordt de letterlijke tekst vertaald als hierboven, behalve dat de__result
-initialisatie wordt vertaald als:Span<T1> __result = new T1[__len]; // same assignments as the array translation
De vertaling kan
stackalloc T1[]
of een inline array gebruiken in plaats vannew T1[]
als span-veiligheid wordt gehandhaafd.Als
T
eenReadOnlySpan<T1>
is, wordt de tekst op dezelfde manier vertaald als voor hetSpan<T1>
geval, behalve dat het uiteindelijke resultaat zal zijn datSpan<T1>
impliciet omgezet wordt naar eenReadOnlySpan<T1>
.Een
ReadOnlySpan<T1>
waarbijT1
een primitief type is en alle verzamelingselementen constant zijn, hoeven de gegevens niet op de heap of op de stapel te staan. Een implementatie kan dit bereik bijvoorbeeld rechtstreeks samenstellen als een verwijzing naar een gedeelte van het gegevenssegment van het programma.De bovenstaande formulieren (voor matrices en spanten) zijn de basisweergaven van de verzamelingsexpressie en worden gebruikt voor de volgende vertaalregels:
Als
T
eenC<S0, S1, …>
is die een overeenkomende create-methodB.M<U0, U1, …>()
heeft, wordt het letterlijk vertaald als:// Collection literal is passed as is as the single B.M<...>(...) argument C<S0, S1, …> __result = B.M<S0, S1, …>([...])
Aangezien de methode maken een argumenttype van een geïnstantieerd
ReadOnlySpan<T>
moet hebben, is de vertaalregel voor spans van toepassing wanneer de verzamelingsexpressie wordt doorgegeven aan de methode voor maken.Als
T
ondersteuning biedt voor initialisatieprogramma's voor verzamelingen, dan:als het type
T
een toegankelijke constructor bevat met één parameterint capacity
, wordt de letterlijke waarde omgezet als:T __result = new T(capacity: __len); __result.Add(__e1); foreach (var __t in __s1) __result.Add(__t); // further additions of the remaining elements
Opmerking: de naam van de parameter moet worden
capacity
.Met dit formulier kan een letterlijke waarde het nieuw geconstrueerde type informeren over het aantal elementen, zodat interne opslag efficiënt kan worden toegewezen. Dit voorkomt onnodige herverdelingen naarmate de elementen worden toegevoegd.
anders wordt de letterlijke tekst vertaald als:
T __result = new T(); __result.Add(__e1); foreach (var __t in __s1) __result.Add(__t); // further additions of the remaining elements
Hierdoor kan het doeltype worden gemaakt, hoewel er geen capaciteitsoptimalisatie is om interne herlocatie van opslag te voorkomen.
Onbekende lengtevertaling
Gezien een doeltype
T
voor een onbekende lengte letterlijke:Als
T
ondersteuning biedt voor initialisatieprogramma's voor verzamelingen, wordt de letterlijke tekst vertaald als:T __result = new T(); __result.Add(__e1); foreach (var __t in __s1) __result.Add(__t); // further additions of the remaining elements
Dit maakt het verspreiden van elk itereerbaar type mogelijk, zij het met de minste hoeveelheid optimalisatie mogelijk.
Als
T
eenT1[]
is, heeft de literal dezelfde semantiek als:List<T1> __list = [...]; /* initialized using predefined rules */ T1[] __result = __list.ToArray();
Het bovenstaande is echter inefficiënt; het creëert eerst de tussenliggende lijst en vervolgens wordt daarvan een kopie van de uiteindelijke array gemaakt. Implementaties zijn gratis om dit weg te optimaliseren, bijvoorbeeld om code als volgt te produceren:
T1[] __result = <private_details>.CreateArray<T1>( count_of_expression_elements); int __index = 0; <private_details>.Add(ref __result, __index++, __e1); foreach (var __t in __s1) <private_details>.Add(ref __result, __index++, __t); // further additions of the remaining elements <private_details>.Resize(ref __result, __index);
Dit maakt minimale verspilling en kopiëren mogelijk, zonder extra overhead die bibliotheekverzamelingen kunnen oplopen.
De aantallen die worden doorgegeven aan
CreateArray
worden gebruikt om een hint voor de initiële grootte te bieden om onnodig hergroottes te voorkomen.Als
T
een typeis, zou een implementatie de hierboven beschrevenT[]
strategie kunnen volgen, of een andere strategie met dezelfde semantiek, maar betere prestaties. In plaats van de matrix bijvoorbeeld toe te wijzen als een kopie van de lijstelementen, kanCollectionsMarshal.AsSpan(__list)
worden gebruikt om rechtstreeks een spanwaarde te verkrijgen.
Niet ondersteunde scenario's
Hoewel letterlijke verzamelingen voor veel scenario's kunnen worden gebruikt, zijn er een paar scenario's die zij niet kunnen vervangen. Dit zijn onder andere:
- Multidimensionale matrices (bijvoorbeeld
new int[5, 10] { ... }
). Er is geen optie om de dimensies op te nemen, en alle verzamelingsliteralen zijn alleen lineaire structuren of map-structuren. - Verzamelingen die speciale waarden doorgeven aan hun constructors. Er is geen mogelijkheid om toegang te krijgen tot de constructor die wordt gebruikt.
- Geneste initialisaties van verzamelingen, bijvoorbeeld
new Widget { Children = { w1, w2, w3 } }
. Dit formulier moet blijven omdat het zeer verschillende semantiek heeft vanChildren = [w1, w2, w3]
. Eerstgenoemde roept herhaaldelijk.Add
aan op.Children
, terwijl de laatstgenoemde een nieuwe verzameling zou toewijzen over.Children
. Als.Children
niet kan worden toegewezen, kunnen we overwegen om het laatste formulier terug te laten vallen op toevoegen aan een bestaande collectie, maar dat lijkt erg verwarrend te kunnen zijn.
Dubbelzinnigheid van syntaxis
Er zijn twee 'echte' syntactische ambiguïteiten waarbij er meerdere juridische syntactische interpretaties van code zijn die een
collection_literal_expression
gebruiken.De
spread_element
is dubbelzinnig met eenrange_expression
. Technisch gezien had u het volgende kunnen hebben:Range[] ranges = [range1, ..e, range2];
Om dit op te lossen, kunnen we het volgende doen:
- Vereisen dat gebruikers
(..e)
tussen haakjes plaatsen of een startindex0..e
opnemen, wanneer ze een bereik willen. - Kies een andere syntaxis (zoals
...
) voor verspreiding. Dit zou jammer zijn vanwege het gebrek aan consistentie met segmentpatronen.
- Vereisen dat gebruikers
Er zijn twee gevallen waarin er geen echte dubbelzinnigheid is, maar waarbij de syntaxis de parseringscomplexiteit aanzienlijk verhoogt. Hoewel er geen probleem is met de technische tijd, neemt dit de cognitieve overhead voor gebruikers nog steeds toe bij het bekijken van code.
Dubbelzinnigheid tussen
collection_literal_expression
enattributes
bij statements of lokale functies. Overwegen:[X(), Y, Z()]
Dit kan een van de volgende zijn:
// A list literal inside some expression statement [X(), Y, Z()].ForEach(() => ...); // The attributes for a statement or local function [X(), Y, Z()] void LocalFunc() { }
Zonder complexe lookahead zou het onmogelijk zijn om dit te bepalen zonder de gehele letterlijke tekst te consumeren.
Opties om dit aan te pakken zijn onder andere:
- Hiermee kunt u het parseringswerk uitvoeren om te bepalen welke van deze gevallen dit is.
- Laat dit niet toe en vereist dat de gebruiker de letterlijke tekst tussen haakjes verpakt, zoals
([X(), Y, Z()]).ForEach(...)
. - Onduidelijkheid tussen een
collection_literal_expression
in eenconditional_expression
en eennull_conditional_operations
. Overwegen:
M(x ? [a, b, c]
Dit kan een van de volgende zijn:
// A ternary conditional picking between two collections M(x ? [a, b, c] : [d, e, f]); // A null conditional safely indexing into 'x': M(x ? [a, b, c]);
Zonder complexe lookahead zou het onmogelijk zijn om dit te bepalen zonder de gehele letterlijke tekst te consumeren.
Opmerking: dit is een probleem, zelfs zonder een natuurlijk type omdat het doeltype wordt toegepast via
conditional_expressions
.Net als bij de andere kunnen we haakjes nodig hebben om ondubbelzinnig te zijn. Met andere woorden, neem de
null_conditional_operation
interpretatie aan, tenzij deze als zodanig is geschreven:x ? ([1, 2, 3]) :
. Dat lijkt echter nogal jammer. Dit soort code lijkt niet onredelijk om te schrijven en zal waarschijnlijk voor verwarring zorgen.
Nadelen
- Dit introduceert nog een andere vorm voor verzamelingsexpressies bovenop de talloze manieren die we al hebben. Dit is extra complexiteit voor de taal. Dit gezegd zijnde, maakt dit het ook mogelijk om één syntax voor
ringom ze allemaal te beheersen, wat betekent dat bestaande codebases overal kunnen worden vereenvoudigd en gebracht naar een uniform uiterlijk. - Het gebruik van
[
...]
in plaats van{
...}
wijkt af van de syntaxis die we over het algemeen al hebben gebruikt voor matrices en verzameling-initializers. Specifiek dat het[
...]
gebruikt in plaats van{
...}
. Dit werd echter al geregeld door het taalteam toen we lijsten van patronen maakten. We hebben geprobeerd om{
...}
met lijstpatronen te laten werken en stuitten daarbij op onoverkomelijke problemen. Daarom zijn we verhuisd naar[
...]
die, terwijl nieuw voor C#, natuurlijk voelt in veel programmeertalen en ons in staat stelt om fris te beginnen zonder dubbelzinnigheid. Het gebruik van[
...]
als de bijbehorende letterlijke vorm is een aanvulling op onze meest recente beslissingen en geeft ons een schone plek om zonder problemen te werken.
Dit introduceert wratten in de taal. Bijvoorbeeld, de volgende zijn zowel legaal als (gelukkig) van precies dezelfde betekenis:
int[] x = { 1, 2, 3 };
int[] x = [ 1, 2, 3 ];
Gezien de breedte en consistentie die door de nieuwe letterlijke notatie worden gebracht, moeten we echter overwegen aan te raden dat mensen overstappen op de nieuwe vorm. IDE-suggesties en oplossingen kunnen hierbij helpen.
Alternatieven
- Welke andere ontwerpen zijn overwogen? Wat is de impact van dit niet doen?
Opgeloste vragen
Moet de compiler
stackalloc
gebruiken voor stacktoewijzing wanneer inlinematrices niet beschikbaar zijn en het iteratietype een primitief type is?Resolutie: Nee. Voor het beheren van een
stackalloc
buffer is extra inspanning vereist vergeleken met een inlinematrix om ervoor te zorgen dat de buffer niet herhaaldelijk wordt toegewezen wanneer de verzamelingsuitdrukking binnen een lus valt. De extra complexiteit in de compiler en in de gegenereerde code weegt op tegen het voordeel van stacktoewijzing op oudere platforms.In welke volgorde moeten we letterlijke elementen evalueren vergeleken met de eigenschap Length/Count? Moeten we eerst alle elementen evalueren, dan alle lengten? Of moeten we een element evalueren, dan de lengte, het volgende element, enzovoort?
Oplossing: We evalueren eerst alle elementen en daarna volgen alle andere elementen.
Kan een onbekende lengte letterlijke waarde een verzamelingstype maken dat een bekende lengte nodig heeft, zoals een array-, span- of Construct(array/span)-verzameling? Dit zou moeilijker zijn om efficiënt te doen, maar het kan mogelijk zijn door slim gebruik te maken van gegroepeerde matrices en/of opbouwfuncties.
Oplossing: Ja, we maken het mogelijk om een verzameling met een vaste lengte te maken van een onbekende lengte letterlijk. De compiler mag dit zo efficiënt mogelijk implementeren.
De volgende tekst bestaat om de oorspronkelijke discussie van dit onderwerp vast te leggen.
Gebruikers konden altijd een onbekende lengte omzetten naar een bekende lengte met code zoals:
ImmutableArray<int> x = [a, ..unknownLength.ToArray(), b];
Dit is echter helaas het gevolg van de noodzaak om toewijzingen van tijdelijke opslag af te dwingen. We kunnen mogelijk efficiënter zijn als we bepalen hoe dit is verzonden.
Kan een
collection_expression
worden omgezet naar eenIEnumerable<T>
of andere collectieinterfaces?Bijvoorbeeld:
void DoWork(IEnumerable<long> values) { ... } // Needs to produce `longs` not `ints` for this to work. DoWork([1, 2, 3]);
Oplossing: Ja, een literal kan een doeltype zijn voor elk interfacetype
I<T>
datList<T>
implementeert. BijvoorbeeldIEnumerable<long>
. Dit is hetzelfde als het target-type omzetten naarList<long>
en dat resultaat vervolgens toewijzen aan het opgegeven interfacetype. De volgende tekst bestaat om de oorspronkelijke discussie van dit onderwerp vast te leggen.De open vraag hier is bepalen welk onderliggende type daadwerkelijk moet worden gemaakt. Een optie is om naar het voorstel voor
params IEnumerable<T>
te kijken. Daar genereren we een matrix om de waarden door te geven, vergelijkbaar met wat er gebeurt metparams T[]
.Kan/moet de compiler
Array.Empty<T>()
verzenden voor[]
? Moeten wij dit verplicht stellen om toewijzingen waar mogelijk te vermijden?Ja. De compiler moet
Array.Empty<T>()
verzenden voor elk geval waarin dit juridisch is en het uiteindelijke resultaat niet veranderlijk is. Bijvoorbeeld het richten opT[]
,IEnumerable<T>
,IReadOnlyCollection<T>
ofIReadOnlyList<T>
. Het mag geenArray.Empty<T>
gebruiken wanneer het doel veranderlijk is (ICollection<T>
ofIList<T>
).Moeten we verder gaan met initialisatie van verzamelingen om te zoeken naar de zeer gangbare
AddRange
methode? Het kan worden gebruikt door het onderliggende samengestelde type om het toevoegen van verspreide elementen mogelijk efficiënter uit te voeren. Misschien willen we ook zoeken naar zaken zoals.CopyTo
. Er kunnen hier nadelen zijn, omdat deze methoden tot gevolg kunnen hebben dat overtollige toewijzingen/verzendingen worden veroorzaakt versus dat deze rechtstreeks in de vertaalde code worden opgesomd.Ja. Een implementatie mag andere methoden gebruiken om een verzamelingswaarde te initialiseren, volgens het vermoeden dat deze methoden goed gedefinieerde semantiek hebben en dat verzamelingstypen 'goed gedragen' moeten zijn. In de praktijk moet een implementatie echter voorzichtig zijn, aangezien voordelen op de ene manier (bulk kopiëren) ook negatieve gevolgen kunnen hebben (bijvoorbeeld het boxen van een structverzameling).
Een implementatie moet profiteren in de gevallen waarin er geen nadelen zijn. Bijvoorbeeld met een
.AddRange(ReadOnlySpan<T>)
methode.
Niet-opgeloste vragen
- Moeten we het elementtype toestaan wanneer het iteratietype 'dubbelzinnig' is (volgens een bepaalde definitie)? Bijvoorbeeld:
Collection x = [1L, 2L];
// error CS1640: foreach statement cannot operate on variables of type 'Collection' because it implements multiple instantiations of 'IEnumerable<T>'; try casting to a specific interface instantiation
foreach (var x in new Collection) { }
static class Builder
{
public Collection Create(ReadOnlySpan<long> items) => throw null;
}
[CollectionBuilder(...)]
class Collection : IEnumerable<int>, IEnumerable<string>
{
IEnumerator<int> IEnumerable<int>.GetEnumerator() => throw null;
IEnumerator<string> IEnumerable<string>.GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
}
Moet het juridisch zijn om een letterlijke verzameling te maken en onmiddellijk te indexeren? Opmerking: dit vereist een antwoord op de onopgeloste vraag hieronder van of letterlijke verzamelingen een natuurlijk type hebben.
Stackallocaties voor enorme verzamelingen kunnen leiden tot een stack overflow. Moet de compiler een heuristiek hebben voor het plaatsen van deze gegevens op de heap? Moet de taal niet worden opgegeven om deze flexibiliteit mogelijk te maken? We moeten de specificatie voor
params Span<T>
volgen.Moeten we het doeltype
spread_element
specificeren? Denk bijvoorbeeld aan:Span<int> span = [a, ..b ? [c] : [d, e], f];
Opmerking: dit kan meestal in de volgende vorm worden weergegeven om voorwaardelijke opname van bepaalde elementen toe te staan, of niets als de voorwaarde onwaar is:
Span<int> span = [a, ..b ? [c, d, e] : [], f];
Om deze volledige literal te evalueren, moeten we de expressies van de elementen erin evalueren. Dat betekent dat u
b ? [c] : [d, e]
kunt evalueren. Als er echter geen doeltype is om deze expressie in de betreffende context te evalueren, en afwezigheid van een enig natuurlijk type, zouden we dan niet kunnen bepalen wat we met[c]
of[d, e]
hier moeten doen.Om dit op te lossen, zouden we kunnen zeggen dat er bij het evalueren van een letterlijke
spread_element
-expressie een impliciet doeltype aanwezig is dat gelijkwaardig is aan het doeltype van de letterlijke zelf. In het bovenstaande zou dat dus worden herschreven als:int __e1 = a; Span<int> __s1 = b ? [c] : [d, e]; int __e2 = f; Span<int> __result = stackalloc int[2 + __s1.Length]; int __index = 0; __result[__index++] = a; foreach (int __t in __s1) __result[index++] = __t; __result[__index++] = f; Span<int> span = __result;
Specificatie van een constructible verzamelingstype dat gebruikmaakt van een creëermethode is gevoelig voor de context waarin de conversie wordt geclassificeerd.
Het bestaan van de conversie in dit geval is afhankelijk van de notie van een iteratietype van het verzamelingstype. Als er een creatiemethode is die een ReadOnlySpan<T>
neemt waarbij T
het iteratietypeis, bestaat de conversie. Anders niet.
Een iteratietype is echter gevoelig voor de context waarop foreach
wordt uitgevoerd. Voor hetzelfde verzamelingstype kan het verschillend zijn op basis van welke uitbreidingsmethoden binnen het bereik vallen en kan het ook niet worden gedefinieerd.
Dat voelt goed voor het doel van foreach
wanneer het type niet is ontworpen om op zichzelf iteratief te zijn. Als dit het geval is, kunnen extensiemethoden niet wijzigen hoe het type wordt overgeschakeld, ongeacht wat de context is.
Dat voelt echter enigszins vreemd aan voor een conversie om zo contextgevoelig te zijn. In feite is de conversie "instabiel". Een verzamelingstype dat expliciet is ontworpen om construeerbaar te zijn, kan afzien van een definitie van een zeer belangrijk detail - het iteratietype. Laat het type 'niet converteerbaar' op zichzelf staan.
Hier volgt een voorbeeld:
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
[CollectionBuilder(typeof(MyCollectionBuilder), nameof(MyCollectionBuilder.Create))]
class MyCollection
{
}
class MyCollectionBuilder
{
public static MyCollection Create(ReadOnlySpan<long> items) => throw null;
public static MyCollection Create(ReadOnlySpan<string> items) => throw null;
}
namespace Ns1
{
static class Ext
{
public static IEnumerator<long> GetEnumerator(this MyCollection x) => throw null;
}
class Program
{
static void Main()
{
foreach (var l in new MyCollection())
{
long s = l;
}
MyCollection x1 = ["a", // error CS0029: Cannot implicitly convert type 'string' to 'long'
2];
}
}
}
namespace Ns2
{
static class Ext
{
public static IEnumerator<string> GetEnumerator(this MyCollection x) => throw null;
}
class Program
{
static void Main()
{
foreach (var l in new MyCollection())
{
string s = l;
}
MyCollection x1 = ["a",
2]; // error CS0029: Cannot implicitly convert type 'int' to 'string'
}
}
}
namespace Ns3
{
class Program
{
static void Main()
{
// error CS1579: foreach statement cannot operate on variables of type 'MyCollection' because 'MyCollection' does not contain a public instance or extension definition for 'GetEnumerator'
foreach (var l in new MyCollection())
{
}
MyCollection x1 = ["a", 2]; // error CS9188: 'MyCollection' has a CollectionBuilderAttribute but no element type.
}
}
}
Gezien het huidige ontwerp kan compiler, als het type niet iteratietype zelf definieert, een toepassing van een CollectionBuilder
kenmerk niet betrouwbaar valideren. Als we het iteratietypeniet kennen, weten we niet wat de signatuur van de creërende methode moet zijn. Als het iteratietype afkomstig is van context, is er geen garantie dat het type altijd in een vergelijkbare context wordt gebruikt.
Params-verzamelingen functie wordt hierdoor ook beïnvloed. Het voelt vreemd om het elementtype van een params
parameter op het declaratiepunt niet betrouwbaar te voorspellen. Het huidige voorstel moet er ook voor zorgen dat de methode maken minstens zo toegankelijk is als het params
verzamelingstype. Het is onmogelijk om deze controle op een betrouwbare manier uit te voeren, tenzij het verzamelingstype het iteratietype zelf definieert.
Houd er rekening mee dat we ook https://github.com/dotnet/roslyn/issues/69676 hebben geopend voor de compiler, die in feite hetzelfde probleem waarneemt, maar waarnaar vanuit het perspectief van optimalisatie wordt gekeken.
Voorstel
Vereist een type dat het CollectionBuilder
kenmerk gebruikt om zijn eigen iteratie type te definiëren.
Met andere woorden, dit betekent dat het type IEnumarable
/IEnumerable<T>
moet implementeren of dat het een openbare GetEnumerator
methode met de juiste handtekening moet hebben (dit sluit uitbreidingsmethoden uit).
Op dit moment is de maakmethode vereist om toegankelijk te zijn op de plaats waar de collectie-expressie wordt gebruikt. Dit is een ander punt van contextafhankelijkheid op basis van toegankelijkheid. Het doel van deze methode is vergelijkbaar met het doel van een door de gebruiker gedefinieerde conversiemethode en die moet openbaar zijn. Daarom moeten we overwegen om ook de methode maken openbaar te maken.
Conclusie
Goedgekeurd met wijzigingen LDM-2024-01-08
Het idee van iteratietype wordt niet consistent toegepast tijdens conversies
- Voor een structuurtype of klassetype waarbij
System.Collections.Generic.IEnumerable<T>
wordt geïmplementeerd:
- Voor elk element
Ei
is er een impliciete conversie naarT
.
Het lijkt erop dat er een aanname wordt gedaan dat T
in dit geval noodzakelijk het iteratietype van de structuur of het klasse-type is.
Deze aanname is echter onjuist. Wat kan leiden tot een heel vreemd gedrag. Bijvoorbeeld:
using System.Collections;
using System.Collections.Generic;
class MyCollection : IEnumerable<long>
{
IEnumerator<long> IEnumerable<long>.GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
public void Add(string l) => throw null;
public IEnumerator<string> GetEnumerator() => throw null;
}
class Program
{
static void Main()
{
foreach (var l in new MyCollection())
{
string s = l; // Iteration type is string
}
MyCollection x1 = ["a", // error CS0029: Cannot implicitly convert type 'string' to 'long'
2];
MyCollection x2 = new MyCollection() { "b" };
}
}
- Voor een struct of klassetype die
System.Collections.IEnumerable
implementeert en geenSystem.Collections.Generic.IEnumerable<T>
implementeert.
Het lijkt erop dat de implementatie ervan uitgaat dat het iteratietypeobject
is, maar de specificatie laat dit feit onvermeld en vereist simpelweg niet dat elk element in iets wordt omgezet. Over het algemeen is het iteratietype echter niet noodzakelijk het object
type. Dit kan worden waargenomen in het volgende voorbeeld:
using System.Collections;
using System.Collections.Generic;
class MyCollection : IEnumerable
{
public IEnumerator<string> GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
}
class Program
{
static void Main()
{
foreach (var l in new MyCollection())
{
string s = l; // Iteration type is string
}
}
}
Het concept van iteratietype is fundamenteel voor de Params verzameling functie . En dit probleem leidt tot een vreemde discrepantie tussen de twee functies. Bijvoorbeeld:
using System.Collections;
using System.Collections.Generic;
class MyCollection : IEnumerable<long>
{
IEnumerator<long> IEnumerable<long>.GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
public IEnumerator<string> GetEnumerator() => throw null;
public void Add(long l) => throw null;
public void Add(string l) => throw null;
}
class Program
{
static void Main()
{
Test("2"); // error CS0029: Cannot implicitly convert type 'string' to 'long'
Test(["2"]); // error CS1503: Argument 1: cannot convert from 'collection expressions' to 'string'
Test(3); // error CS1503: Argument 1: cannot convert from 'int' to 'string'
Test([3]); // Ok
MyCollection x1 = ["2"]; // error CS0029: Cannot implicitly convert type 'string' to 'long'
MyCollection x2 = [3];
}
static void Test(params MyCollection a)
{
}
}
using System.Collections;
using System.Collections.Generic;
class MyCollection : IEnumerable
{
IEnumerator IEnumerable.GetEnumerator() => throw null;
public IEnumerator<string> GetEnumerator() => throw null;
public void Add(object l) => throw null;
}
class Program
{
static void Main()
{
Test("2", 3); // error CS1503: Argument 2: cannot convert from 'int' to 'string'
Test(["2", 3]); // Ok
}
static void Test(params MyCollection a)
{
}
}
Het zal waarschijnlijk goed zijn om voor de ene of de andere optie te kiezen.
Voorstel
Geef de conversiebaarheid op van struct of klassetype die System.Collections.Generic.IEnumerable<T>
of System.Collections.IEnumerable
implementeert in termen van iteratietype en waarvoor een impliciete conversie voor elk elementEi
is vereist voor het iteratietype.
Conclusie
Goedgekeurd LDM-2024-01-08
Moet conversie van verzamelingsexpressies beschikbaarheid van een minimale set API's voor constructie vereisen?
Een constructible verzamelingstype volgens conversies kan eigenlijk niet constructeerbaar zijn, wat waarschijnlijk leidt tot een onverwacht overbelastingsresolutiegedrag. Bijvoorbeeld:
class C1
{
public static void M1(string x)
{
}
public static void M1(char[] x)
{
}
void Test()
{
M1(['a', 'b']); // error CS0121: The call is ambiguous between the following methods or properties: 'C1.M1(string)' and 'C1.M1(char[])'
}
}
Echter, de 'C1. M1(tekenreeks)' is geen kandidaat die kan worden gebruikt omdat:
error CS1729: 'string' does not contain a constructor that takes 0 arguments
error CS1061: 'string' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?)
Hier volgt een ander voorbeeld met een door de gebruiker gedefinieerd type en een sterkere fout die zelfs geen geldige kandidaat vermeldt:
using System.Collections;
using System.Collections.Generic;
class C1 : IEnumerable<char>
{
public static void M1(C1 x)
{
}
public static void M1(char[] x)
{
}
void Test()
{
M1(['a', 'b']); // error CS1061: 'C1' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'C1' could be found (are you missing a using directive or an assembly reference?)
}
public static implicit operator char[](C1 x) => throw null;
IEnumerator<char> IEnumerable<char>.GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
}
Het lijkt erop dat de situatie vergelijkbaar is met wat we met de methodegroep hebben gebruikt om conversies te delegeren. Er waren scenario's waarin de conversie bestond, maar er was sprake van onjuiste gegevens. We besloten dat te verbeteren door ervoor te zorgen dat, als conversie onjuist is, het niet bestaat.
Houd er rekening mee dat er met de functie "Params-verzamelingen" een vergelijkbaar probleem optreedt. Het kan handig zijn om het gebruik van params
wijzigingsfunctie voor niet-constructibele verzamelingen niet toe te staan. In het huidige voorstel is die controle echter gebaseerd op conversies sectie. Hier volgt een voorbeeld:
using System.Collections;
using System.Collections.Generic;
class C1 : IEnumerable<char>
{
public static void M1(params C1 x) // It is probably better to report an error about an invalid `params` modifier
{
}
public static void M1(params ushort[] x)
{
}
void Test()
{
M1('a', 'b'); // error CS1061: 'C1' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'C1' could be found (are you missing a using directive or an assembly reference?)
M2('a', 'b'); // Ok
}
public static void M2(params ushort[] x)
{
}
IEnumerator<char> IEnumerable<char>.GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
}
Het lijkt erop dat het probleem eerder enigszins is besproken, zie https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-10-02.md#collection-expressions. Op dat moment is er een argument aangevoerd dat de regels, zoals nu opgegeven, consistent zijn met de wijze waarop geïnterpoleerde tekenreekshandlers worden opgegeven. Hier volgt een offerte:
Met name werden geïnterpoleerde tekenreekshandlers oorspronkelijk op deze wijze gespecificeerd, maar we hebben de specificatie herzien na dit probleem in overweging te hebben genomen.
Hoewel er enige gelijkenis is, is er ook een belangrijk onderscheid dat het overwegen waard is. Hier volgt een citaat uit https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/improved-interpolated-strings.md#interpolated-string-handler-conversion:
Type
T
wordt beschouwd als een applicable_interpolated_string_handler_type als het wordt toegeschreven metSystem.Runtime.CompilerServices.InterpolatedStringHandlerAttribute
. Er bestaat een impliciete interpolated_string_handler_conversion naarT
van een interpolated_string_expressionof een additive_expression die volledig uit _interpolated_string_expression_s bestaat en uitsluitend+
operators gebruikt.
Het doeltype moet een speciaal kenmerk hebben dat een sterke aanwijzing is voor de bedoeling van de auteur dat het type een geïnterpoleerde tekenreeksverwerker moet zijn. Het is eerlijk om aan te nemen dat aanwezigheid van het kenmerk geen toeval is.
Het feit dat een type 'enumerable' is, betekent daarentegen niet dat de bedoeling van de auteur is dat het type kan worden samengesteld. De aanwezigheid van een creëer-methode, die echter wordt aangeduid met een [CollectionBuilder(...)]
kenmerk op het verzamelingstype, voelt als een sterke aanwijzing voor de intentie van de auteur dat het type geconstrueerd kan worden.
Voorstel
Voor een struct of klassetype die System.Collections.IEnumerable
implementeert en geen creatiemethode heeft, moet er in de conversies sectie ten minste de volgende APIs aanwezig zijn:
- Een toegankelijke constructor die van toepassing is zonder argumenten.
- Een toegankelijke
Add
instantie- of extensiemethode die kan worden aangeroepen met de waarde van iteratietype als argument.
Voor de functie Params Collections zijn dergelijke typen geldig params
typen wanneer deze API's als openbaar worden verklaard en instantiemethoden zijn in plaats van extensiemethoden.
Conclusie
Goedgekeurd met wijzigingen LDM-2024-01-10
Ontwerpvergaderingen
https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-11-01.md#collection-literals https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-03-09.md#ambiguity-of--in-collection-expressions https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-09-28.md#collection-literals https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-08.md https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-10.md
Werkgroepvergaderingen
https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/CL-2022-10-06.md https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/CL-2022-10-14.md https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/CL-2022-10-21.md https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/CL-2023-04-05.md https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/CL-2023-04-28.md https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/CL-2023-05-26.md https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/CL-2023-06-12.md https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/CL-2023-06-26.md https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/CL-2023-08-03.md https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/CL-2023-08-10.md
Aankomende agendapunten
Stackallocaties voor enorme verzamelingen kunnen leiden tot een stack overflow. Moet de compiler een heuristiek hebben voor het plaatsen van deze gegevens op de heap? Moet de taal niet worden opgegeven om deze flexibiliteit mogelijk te maken? We moeten volgen wat de spec/impl doet voor
params Span<T>
. Opties zijn:- Altijd stackalloc gebruiken. Leer mensen om voorzichtig te zijn met Span. Hierdoor kunnen zaken zoals
Span<T> span = [1, 2, ..s]
werken en prima zijn zolangs
klein is. Als dit de stack laat overlopen, kunnen gebruikers altijd een array maken en vervolgens een span hieromheen vereisen. Dit lijkt het meest in lijn met wat mensen willen, maar met extreem gevaar. - Alleen stackalloc gebruiken wanneer de letterlijke waarde een vast aantal elementen bevat, dus zonder verspreide elementen. Dit maakt dingen dan waarschijnlijk altijd veilig, met vast stackgebruik en de compiler (hopelijk) die vaste buffer opnieuw kan gebruiken. Het betekent echter dat dingen zoals
[1, 2, ..s]
nooit mogelijk zouden zijn, zelfs als de gebruiker weet dat het tijdens runtime volledig veilig is.
- Altijd stackalloc gebruiken. Leer mensen om voorzichtig te zijn met Span. Hierdoor kunnen zaken zoals
Hoe werkt overbelastingsresolutie? Als een API het volgende heeft:
public void M(T[] values); public void M(List<T> values);
Wat gebeurt er met
M([1, 2, 3])
? We moeten waarschijnlijk 'beterheid' definiëren voor deze conversies.Moeten we verder gaan met initialisatie van verzamelingen om te zoeken naar de zeer gangbare
AddRange
methode? Het kan worden gebruikt door het onderliggende samengestelde type om het toevoegen van verspreide elementen mogelijk efficiënter uit te voeren. Misschien willen we ook zoeken naar zaken zoals.CopyTo
. Er kunnen hier nadelen zijn, omdat deze methoden tot gevolg kunnen hebben dat overtollige toewijzingen/verzendingen worden veroorzaakt versus dat deze rechtstreeks in de vertaalde code worden opgesomd.Algemene typedeductie moet worden bijgewerkt om typegegevens naar/van letterlijke gegevens van verzamelingen te stromen. Bijvoorbeeld:
void M<T>(T[] values); M([1, 2, 3]);
Het lijkt natuurlijk dat dit iets moet zijn waar het deductie-algoritme rekening mee kan houden. Zodra dit wordt ondersteund voor de 'base' constructible collection type cases (
T[]
,I<T>
,Span<T>
new T()
), moet deze ook uit deCollect(constructible_type)
case vallen. Bijvoorbeeld:void M<T>(ImmutableArray<T> values); M([1, 2, 3]);
Hier kan
Immutable<T>
worden samengesteld via eeninit void Construct(T[] values)
methode. HetT[] values
-type zou dus worden gebruikt met afleiding tegen[1, 2, 3]
, wat leidt tot een afleiding vanint
voorT
.Onduidelijkheid in cast/index.
Vandaag is het volgende een uitdrukking die is geïndexeerd in
var v = (Expr)[1, 2, 3];
Maar het zou leuk zijn om dingen te kunnen doen zoals:
var v = (ImmutableArray<int>)[1, 2, 3];
Kunnen we hier even pauze nemen?
Syntactische dubbelzinnigheid met
?[
.Het kan de moeite waard zijn om de regels voor
nullable index access
te wijzigen zodat er geen ruimte kan optreden tussen?
en[
. Dat zou een brekende verandering zijn (maar waarschijnlijk minder belangrijk, omdat Visual Studio deze al samenvoegt als je ze typt met een spatie). Als we dit doen, kanx?[y]
anders worden geparseerd danx ? [y]
.Er gebeurt iets vergelijkbaars als we met https://github.com/dotnet/csharplang/issues/2926willen gaan. In die wereld is
x?.y
verwarrend ten opzichte vanx ? .y
. Als we de?.
nodig hebben om aan te sluiten, kunnen we eenvoudig syntactisch onderscheid maken tussen de twee gevallen.
C# feature specifications