params Collections
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 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.
Samenvatting
In C# 12 is ondersteuning toegevoegd voor het maken van instanties van verzamelingstypen, niet alleen van arrays.
Zie verzamelingsuitdrukkingen.
Dit voorstel breidt params
ondersteuning uit voor al deze verzamelingstypen.
Motivatie
Een params
matrixparameter biedt een handige manier om een methode aan te roepen die een willekeurige lengtelijst met argumenten gebruikt.
Vandaag moet params
parameter een matrixtype zijn. Het kan echter nuttig zijn voor een ontwikkelaar om hetzelfde gemak te hebben bij het aanroepen van API's die andere verzamelingstypen gebruiken. Bijvoorbeeld een ImmutableArray<T>
, ReadOnlySpan<T>
of een simpele IEnumerable
. Vooral in gevallen waarin compiler een impliciete matrixtoewijzing kan vermijden voor het maken van de verzameling (ImmutableArray<T>
, ReadOnlySpan<T>
, enzovoort).
In situaties waarin een API een verzamelingstype gebruikt, voegen ontwikkelaars meestal een overload params
toe die een array accepteert. Ze stellen de doelverzameling samen en roepen met die verzameling de oorspronkelijke overload aan. Daardoor moeten gebruikers van de API voor het gemak een extra array-toewijzing accepteren.
Een andere motivatie is de mogelijkheid om een params span overload toe te voegen en deze voorrang te geven boven de arrayversie, gewoon door de bestaande broncode opnieuw te compileren.
Gedetailleerd ontwerp
Methodeparameters
De sectie Methodeparameters wordt als volgt aangepast.
formal_parameter_list
: fixed_parameters
- | fixed_parameters ',' parameter_array
+ | fixed_parameters ',' parameter_collection
- | parameter_array
+ | parameter_collection
;
-parameter_array
+parameter_collection
- : attributes? 'params' array_type identifier
+ : attributes? 'params' 'scoped'? type identifier
;
Een parameterverzameling bestaat uit een optionele set kenmerken, een params
modifier, een optionele scoped
modifier, een typeen een identificator. Een parameterverzameling declareert één parameter van het opgegeven type met de opgegeven naam.
Het type van een parameterverzameling moet een van de volgende geldige doeltypen zijn voor een verzamelingsexpressie (zie https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#conversions):
- Een enkeldimensionaal matrixtype
T[]
, in welk geval het elementtype isT
- Een span van het type
System.Span<T>
System.ReadOnlySpan<T>
in welke gevallen het elementtype wordtT
- Een type met een bijpassende instantiëringsmethode, die tenminste zo toegankelijk is als het verklarende lid, en met een bijbehorend elementtype als gevolg van die vaststelling
- Een struct-of klassetype dat
System.Collections.IEnumerable
implementeert:Het type heeft een constructor die zonder argumenten kan worden aangeroepen, en de constructor is minstens zo toegankelijk als het declarerend lid.
Het type heeft een instantiemethode (geen extensie)
Add
waar:- De methode kan worden aangeroepen met één waardeargument.
- Als de methode algemeen is, kunnen de typeargumenten worden afgeleid van het argument.
- De methode is minstens zo toegankelijk als het declarerend lid.
In dat geval is het elementtype het iteratietype van het type.
- Een interface-type
-
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 welke gevallen het elementtype wordtT
-
In een methode-aanroep staat een parameterverzameling toe dat één argument van het opgegeven parametertype wordt opgegeven, of dat er nul of meer argumenten van het elementtype van de verzameling moeten worden opgegeven. Parameterverzamelingen worden verder beschreven in Parameterverzamelingen.
Een parameter_collection kan optreden na een optionele parameter, maar kan geen standaardwaarde hebben. Het weglaten van argumenten voor een parameter_collection zou in plaats daarvan resulteren in het maken van een lege verzameling.
Parameterverzamelingen
De sectie Parameterarrays wordt hernoemd en als volgt aangepast.
Een parameter die is gedeclareerd met een params
modifier is een parameterverzameling. Als een formele parameterlijst een parameterverzameling bevat, moet deze de laatste parameter in de lijst zijn en moet deze van het type zijn dat is opgegeven in methodeparameters sectie.
Opmerking: het is niet mogelijk om de
params
modifier te combineren met de modifiersin
,out
ofref
. eindnotitie
Met een parameterverzameling kunnen argumenten op twee manieren worden opgegeven in een methode-aanroep:
- Het argument voor een parameterverzameling kan één expressie zijn die impliciet kan worden omgezet in het type parameterverzameling. In dit geval fungeert de parameterverzameling precies als een waardeparameter.
- De aanroep kan ook nul of meer argumenten voor de parameterverzameling opgeven, waarbij elk argument een expressie is die impliciet kan worden omgezet in het elementtype van de parameterverzameling. In dit geval maakt de aanroep een exemplaar van het parameterverzamelingstype volgens de regels die zijn opgegeven in Verzamelingsexpressies alsof de argumenten zijn gebruikt als expressie-elementen in een verzamelingsexpressie in dezelfde volgorde en het zojuist gemaakte verzamelingsexemplaren als het werkelijke argument gebruikt. Bij het maken van de verzamelingsinstantie worden de oorspronkelijke onveranderde argumenten gebruikt.
Met uitzondering van het toestaan van een variabel aantal argumenten in een aanroep, is een parameterverzameling precies gelijk aan een waardeparameter van hetzelfde type.
Bij het uitvoeren van overbelastingsresolutie kan een methode met een parameterverzameling van toepassing zijn, in de normale vorm of in de uitgevouwen vorm. De uitgevouwen vorm van een methode is alleen beschikbaar als de normale vorm van de methode niet van toepassing is en alleen als een toepasselijke methode met dezelfde handtekening als het uitgevouwen formulier nog niet is gedeclareerd in hetzelfde type.
Een mogelijke dubbelzinnigheid ontstaat tussen de normale vorm en de uitgebreide vorm van de methode met één parameterverzamelingargument wanneer deze kan worden gebruikt als de parameterverzameling zelf en als het element van de parameterverzameling tegelijk. De dubbelzinnigheid vormt echter geen probleem, omdat het kan worden opgelost door een cast in te voegen of, indien nodig, een verzamelingsexpressie te gebruiken.
Handtekeningen en overbelasting
Alle regels rondom params
modifier in Handtekeningen en overbelasting blijven ongewijzigd.
Toepasselijk functielid
De sectie Toepasselijke functielid wordt als volgt aangepast.
Als een functielid dat een parameterverzameling bevat, niet van toepassing is in de normale vorm, kan het functielid in plaats daarvan van toepassing zijn in de uitgevouwen vorm:
- Als de parameterverzameling geen matrix is, is een uitgevouwen formulier niet van toepassing op taalversieS C# 12 en hieronder.
- Het uitgevouwen formulier wordt samengesteld door de parameterverzameling in de declaratie van het functielid te vervangen door nul of meer waardeparameters van het elementtype van de parameterverzameling zodanig dat het aantal argumenten in de argumentenlijst
A
overeenkomt met het totale aantal parameters. AlsA
minder argumenten heeft dan het aantal vaste parameters in de declaratie van het functielid, kan de uitgevouwen vorm van het functielid niet worden samengesteld en is dus niet van toepassing. - Anders is het uitgevouwen formulier van toepassing als voor elk argument in
A
een van de volgende waar is:- de parameterdoorgiftemodus van het argument is identiek aan de parameter-passingsmodus van de bijbehorende parameter en
- voor een parameter met vaste waarde of een waardeparameter die door de uitbreiding is gemaakt, bestaat er een impliciete conversie van de argumentexpressie tot het type van de bijbehorende parameter, of
- voor een parameter
in
,out
ofref
is het type argumentexpressie identiek aan het type van de bijbehorende parameter.
- de parameterdoorgiftemodus van het argument is waarde en de parameterdoorgiftemodus van de bijbehorende parameter is invoer en er bestaat een impliciete conversie van de argumentexpressie naar het type van de bijbehorende parameter
- de parameterdoorgiftemodus van het argument is identiek aan de parameter-passingsmodus van de bijbehorende parameter en
Beter functielid
De sectie Better function member wordt als volgt aangepast.
Gezien een lijst met argumenten A
met een set argumentexpressies {E₁, E₂, ..., Eᵥ}
en twee toepasselijke functieleden Mᵥ
en Mₓ
met parametertypen {P₁, P₂, ..., Pᵥ}
en {Q₁, Q₂, ..., Qᵥ}
, wordt Mᵥ
gedefinieerd als een beter functielid dan Mₓ
als
- voor elk argument is de impliciete conversie van
Eᵥ
naarQᵥ
niet beter dan de impliciete conversie vanEᵥ
naarPᵥ
, en - voor ten minste één argument is de conversie van
Eᵥ
naarPᵥ
beter dan de conversie vanEᵥ
naarQᵥ
.
Als de parametertypereeksen {P₁, P₂, ..., Pᵥ}
en {Q₁, Q₂, ..., Qᵥ}
gelijkwaardig zijn (dat wil bijvoorbeeld dat elke Pᵢ
een identiteitsconversie heeft naar de bijbehorende Qᵢ
), worden de volgende regels voor tie-breaking toegepast om het betere lid van de functie te bepalen.
- Als
Mᵢ
een niet-generieke methode is enMₑ
een algemene methode is, isMᵢ
beter danMₑ
. - Als
Mᵢ
in de normale vorm van toepassing is enMₑ
een paramsverzameling heeft en alleen van toepassing is in de uitgevouwen vorm, isMᵢ
beter danMₑ
. - Als beide methoden paramsverzamelingen hebben en alleen van toepassing zijn in hun uitgebreide vormen, en als de paramsverzameling van
Mᵢ
minder elementen heeft dan de verzameling parameters vanMₑ
, isMᵢ
beter danMₑ
. - Als
Mᵥ
meer specifieke parametertypen heeft danMₓ
, isMᵥ
beter danMₓ
. Laat{R1, R2, ..., Rn}
en{S1, S2, ..., Sn}
de niet-geïnstantieerde en niet-uitgevouwen parametertypen vanMᵥ
enMₓ
voorstellen.Mᵥ
parametertypen zijn specifieker danMₓ
s als voor elke parameterRx
niet minder specifiek is danSx
, en voor ten minste één parameter isRx
specifieker danSx
:- Een typeparameter is minder specifiek dan een niet-typeparameter.
- Recursief is een samengesteld type specifieker dan een ander samengesteld type (met hetzelfde aantal typeargumenten) als ten minste één typeargument specifieker is en geen typeargument minder specifiek is dan het bijbehorende typeargument in het andere.
- Een matrixtype is specifieker dan een ander matrixtype (met hetzelfde aantal dimensies) als het elementtype van de eerste specifieker is dan het elementtype van de tweede.
- Als anders één lid een niet-getilde operator is en de andere een getilde operator, dan is de niet-getilde beter.
- Als geen van beide functieleden beter is gebleken en alle parameters van
Mᵥ
een corresponderend argument hebben, terwijl standaardargumenten moeten worden vervangen door ten minste één optionele parameter inMₓ
, isMᵥ
beter danMₓ
. - Als voor ten minste één parameter
Mᵥ
de betere keuze voor het doorgeven van parameters gebruikt (§12.6.4.4) dan de bijbehorende parameter inMₓ
en geen van de parameters inMₓ
de betere keuze voor parameterdoorgifte danMᵥ
, isMᵥ
beter danMₓ
. -
Anders, als beide methoden parametersverzamelingen hebben en alleen van toepassing zijn in hun uitgebreide formulieren, is
Mᵢ
beter danMₑ
als dezelfde set argumenten overeenkomt met verzamelingselementen voor beide methoden, en een van de volgende geldt (dit komt overeen met https://github.com/dotnet/csharplang/blob/main/proposals/csharp-13.0/collection-expressions-better-conversion.md):-
beide parametersverzamelingen niet span_types zijn en er bestaat een impliciete conversie van paramsverzameling van
Mᵢ
naar paramsverzameling vanMₑ
-
verzameling parameters van
Mᵢ
isSystem.ReadOnlySpan<Eᵢ>
, en verzameling parameters vanMₑ
isSystem.Span<Eₑ>
, en er bestaat een identiteitsconversie vanEᵢ
naarEₑ
-
verzameling parameters van
Mᵢ
isSystem.ReadOnlySpan<Eᵢ>
ofSystem.Span<Eᵢ>
, en verzameling parameters vanMₑ
is een array_or_array_interface__type met elementtypeEₑ
, en er bestaat een identiteitsconversie vanEᵢ
naarEₑ
-
beide parametersverzamelingen niet span_types zijn en er bestaat een impliciete conversie van paramsverzameling van
- Anders is geen functielid beter.
De reden waarom de nieuwe tie-breaking-regel als laatste in de lijst staat, is het laatste subitem.
- beide parametersverzamelingen zijn niet span_type's, en er bestaat een impliciete conversie van een paramsverzameling van
Mᵢ
naar een paramsverzameling vanMₑ
Het is van toepassing op arrays en daarom zal het eerder uitvoeren van de tie-break een verandering in gedrag voor bestaande scenario's introduceren.
Bijvoorbeeld:
class Program
{
static void Main()
{
Test(1);
}
static void Test(in int x, params C2[] y) {} // There is an implicit conversion from `C2[]` to `C1[]`
static void Test(int x, params C1[] y) {} // Better candidate because of "better parameter-passing choice"
}
class C1 {}
class C2 : C1 {}
Als een van de vorige regels voor tie-breaking van toepassing is (inclusief de regel van "betere argumentconversie"), kan het resultaat van de overbelastingsresolutie verschillen van wanneer een expliciete verzameluitdrukking als argument wordt gebruikt.
Bijvoorbeeld:
class Program
{
static void Test1()
{
M1(['1', '2', '3']); // IEnumerable<char> overload is used because `char` is an exact match
M1('1', '2', '3'); // IEnumerable<char> overload is used because `char` is an exact match
}
static void M1(params IEnumerable<char> value) {}
static void M1(params System.ReadOnlySpan<MyChar> value) {}
class MyChar
{
private readonly int _i;
public MyChar(int i) { _i = i; }
public static implicit operator MyChar(int i) => new MyChar(i);
public static implicit operator char(MyChar c) => (char)c._i;
}
static void Test2()
{
M2([1]); // Span overload is used
M2(1); // Array overload is used, not generic
}
static void M2<T>(params System.Span<T> y){}
static void M2(params int[] y){}
static void Test3()
{
M3("3", ["4"]); // Ambiguity, better-ness of argument conversions goes in opposite directions.
M3("3", "4"); // Ambiguity, better-ness of argument conversions goes in opposite directions.
// Since parameter types are different ("object, string" vs. "string, object"), tie-breaking rules do not apply
}
static void M3(object x, params string[] y) {}
static void M3(string x, params Span<object> y) {}
}
Ons belangrijkste probleem zijn echter scenario's waarbij overbelasting alleen verschilt per type params-verzameling, maar de verzamelingstypen hebben hetzelfde elementtype. Het gedrag moet consistent zijn met expliciete verzamelingsexpressies voor deze gevallen.
De "als dezelfde set argumenten overeenkomt met de verzamelingselementen voor beide methoden" voorwaarde is belangrijk voor scenario's zoals:
class Program
{
static void Main()
{
Test(x: 1, y: 2); // Ambiguous
}
static void Test(int x, params System.ReadOnlySpan<int> y) {}
static void Test(int y, params System.Span<int> x) {}
}
Het voelt niet redelijk om verzamelingen te vergelijken die zijn gebouwd op basis van verschillende elementen.
Deze sectie is beoordeeld bij LDM, en is goedgekeurd.
Een van deze regels is dat wanneer params
van verschillende elementtypen beschikbaar worden gesteld, deze niet eenduidig zijn wanneer ze worden aangeroepen met een lege lijst met argumenten.
Bijvoorbeeld:
class Program
{
static void Main()
{
// Old scenarios
C.M1(); // Ambiguous since params arrays were introduced
C.M1([]); // Ambiguous since params arrays were introduced
// New scenarios
C.M2(); // Ambiguous in C# 13
C.M2([]); // Ambiguous in C# 13
C.M3(); // Ambiguous in C# 13
C.M3([]); // Ambiguous in C# 13
}
public static void M1(params int[] a) {
}
public static void M1(params int?[] a) {
}
public static void M2(params ReadOnlySpan<int> a) {
}
public static void M2(params Span<int?> a) {
}
public static void M3(params ReadOnlySpan<int> a) {
}
public static void M3(params ReadOnlySpan<int?> a) {
}
}
Aangezien we prioriteit geven aan elementtype boven alle andere, lijkt dit redelijk; er is niets om de taal te vertellen of de gebruiker liever int?
dan int
in dit scenario.
Dynamische binding
Uitgebreide vormen van kandidaten die niet-arrayparameterverzamelingen gebruiken, worden door de huidige C# runtime-binder niet als geldige kandidaten beschouwd.
Als de primary_expression geen compilatietijdtype heeft dynamic
, ondergaat de aanroep van de methode een beperkte compileertijdfoutcontrole, zoals beschreven in §12.6.5 Compileertijdcontrole van dynamische ledenaanroep.
Als slechts één kandidaat aan de test voldoet, is de aanroep van de kandidaat statisch gebonden wanneer aan alle volgende voorwaarden wordt voldaan:
- de kandidaat is een lokale functie
- dat de kandidaat niet algemeen is, of de bijbehorende typeargumenten expliciet zijn opgegeven;
- er is geen dubbelzinnigheid tussen normale en uitgebreide vormen van de kandidaat die tijdens het compileren niet kunnen worden opgelost.
Anders is de invocation_expression dynamisch gebonden.
Als slechts één kandidaat de bovenstaande test heeft doorstaan:
- als deze kandidaat een lokale functie is, treedt er een compilatietijdfout op;
- als deze kandidaat alleen van toepassing is in uitgebreide vorm die gebruikmaakt van niet-matrixparamsverzamelingen, treedt er een compilatietijdfout op.
We moeten ook overwegen om de specificatieschendingen terug te zetten of te herstellen die vandaag van invloed zijn op lokale functies, zie https://github.com/dotnet/roslyn/issues/71399.
LDM bevestigd dat we deze schending van de specificatie willen oplossen.
Expressiebomen
Verzamelingsexpressies worden niet ondersteund in expressiebomen. Op dezelfde manier worden uitgebreide vormen van niet-arrayparameterverzamelingen niet ondersteund in expressiebomen. We zullen niet wijzigen hoe de compiler lambdas verbindt voor expressiestructuren met het doel het gebruik van API's te voorkomen die gebruikmaken van uitgebreide vormen van niet-matrixparamsverzamelingen.
Volgorde van evaluatie met niet-matrixverzamelingen in niet-triviale scenario's
Deze sectie is beoordeeld op LDM en is goedgekeurd. Ondanks het feit dat matrixcases afwijken van andere verzamelingen, hoeft de officiële taalspecificatie geen verschillende regels voor matrices op te geven. De afwijkingen kunnen gewoon worden behandeld als een implementatieartefact. Tegelijkertijd zijn we niet van plan om het bestaande gedrag rond matrices te wijzigen.
Benoemde argumenten
Er wordt een verzamelingsexemplaar gemaakt en ingevuld nadat het lexicaal vorige argument is geëvalueerd, maar voordat het lexicaal volgende argument wordt geëvalueerd.
Bijvoorbeeld:
class Program
{
static void Main()
{
Test(b: GetB(), c: GetC(), a: GetA());
}
static void Test(int a, int b, params MyCollection c) {}
static int GetA() => 0;
static int GetB() => 0;
static int GetC() => 0;
}
De evaluatievolgorde is het volgende:
-
GetB
wordt aangeroepen -
MyCollection
wordt gemaakt en ingevuld enGetC
wordt in het proces aangeroepen -
GetA
wordt aangeroepen -
Test
wordt aangeroepen
Opmerking: in het geval van de parametermatrix wordt de matrix gemaakt vlak voordat de doelmethode wordt aangeroepen, nadat alle argumenten zijn geëvalueerd in hun lexicale volgorde.
Samengestelde toewijzing
Er wordt een verzamelingsvoorbeeld gemaakt en gevuld nadat de lexicaal vorige index is geëvalueerd, maar voordat de lexicaal volgende index wordt geëvalueerd. Het exemplaar wordt gebruikt om getter en setter van de doelindexeerfunctie aan te roepen.
Bijvoorbeeld:
class Program
{
static void Test(Program p)
{
p[GetA(), GetC()]++;
}
int this[int a, params MyCollection c] { get => 0; set {} }
static int GetA() => 0;
static int GetC() => 0;
}
De evaluatievolgorde is het volgende:
-
GetA
wordt aangeroepen en in de cache opgeslagen -
MyCollection
wordt gemaakt, ingevuld en in de cache opgeslagen, wordtGetC
in het proces aangeroepen - De getter van de indexeerder wordt aangeroepen met waarden uit de cache voor indexen.
- Resultaat wordt verhoogd
- De setter van de indexer wordt aangeroepen met gecachede waarden voor indexen en het resultaat van de verhoging.
Een voorbeeld met een lege verzameling:
class Program
{
static void Test(Program p)
{
p[GetA()]++;
}
int this[int a, params MyCollection c] { get => 0; set {} }
static int GetA() => 0;
}
De evaluatievolgorde is het volgende:
-
GetA
wordt aangeroepen en in de cache opgeslagen - Er wordt een lege
MyCollection
gemaakt en in de cache opgeslagen - De getter van de indexeerder wordt aangeroepen met gecachede waarden voor de indexen.
- Resultaat wordt verhoogd
- De setter van de indexeerder wordt aangeroepen met in caches opgeslagen waarden voor indexen en het resultaat van de incrementeeroperatie.
Object-initialisatiefunctie
Er wordt een verzamelingsinstantie gemaakt en ingevuld nadat de lexicaal vorige index is geëvalueerd, maar voordat de lexicaal volgende index wordt geëvalueerd. Het exemplaar wordt gebruikt om de getter van de indexeerfunctie zo vaak als nodig aan te roepen, indien van toepassing.
Bijvoorbeeld:
class C1
{
public int F1;
public int F2;
}
class Program
{
static void Test()
{
_ = new Program() { [GetA(), GetC()] = { F1 = GetF1(), F2 = GetF2() } };
}
C1 this[int a, params MyCollection c] => new C1();
static int GetA() => 0;
static int GetC() => 0;
static int GetF1() => 0;
static int GetF2() => 0;
}
De evaluatievolgorde is het volgende:
-
GetA
wordt aangeroepen en in de cache opgeslagen -
MyCollection
wordt gemaakt, ingevuld en in de cache opgeslagen, wordtGetC
in het proces aangeroepen - De getter van de indexeerder wordt aangeroepen met gecachede waarden voor de indexen.
-
GetF1
wordt geëvalueerd en toegewezen aanF1
veld vanC1
opnieuw afgestemd op de vorige stap - De getter van de indexeerder wordt aangeroepen met gecacheerde waarden voor indexen
-
GetF2
wordt geëvalueerd en toegewezen aanF2
veld vanC1
opnieuw afgestemd op de vorige stap
In het geval van een params-array worden de bijbehorende elementen geëvalueerd en in de cache opgeslagen, maar er wordt in plaats daarvan een nieuw exemplaar van een array (met dezelfde waarden erin) gebruikt voor elke aanroep van de getter van de indexator. In het bovenstaande voorbeeld is de volgorde van evaluatie het volgende:
-
GetA
wordt aangeroepen en in de cache opgeslagen -
GetC
wordt aangeroepen en in de cache opgeslagen - De getter van de indexeerfunctie wordt aangeroepen met
GetA
in de cache en een nieuwe matrix die is gevuld metGetC
in de cache -
GetF1
wordt geëvalueerd en toegewezen aan hetF1
veld vanC1
dat is geretourneerd in de vorige stap - De getter van de indexeerfunctie wordt aangeroepen met
GetA
in de cache en een nieuwe matrix die is gevuld metGetC
in de cache -
GetF2
wordt geëvalueerd en toegewezen aanF2
veld vanC1
opnieuw afgestemd op de vorige stap
Een voorbeeld met een lege verzameling:
class C1
{
public int F1;
public int F2;
}
class Program
{
static void Test()
{
_ = new Program() { [GetA()] = { F1 = GetF1(), F2 = GetF2() } };
}
C1 this[int a, params MyCollection c] => new C1();
static int GetA() => 0;
static int GetF1() => 0;
static int GetF2() => 0;
}
De evaluatievolgorde is het volgende:
-
GetA
wordt aangeroepen en in de cache opgeslagen - Er wordt een lege
MyCollection
gemaakt en in de cache opgeslagen - De getter van de indexeerfunctie wordt aangeroepen met waarden in de cache voor indexen
-
GetF1
wordt geëvalueerd en toegewezen aanF1
veld vanC1
opnieuw afgestemd op de vorige stap - De getter van de indexeerder wordt aangeroepen met gecachede waarden voor indexen.
-
GetF2
wordt geëvalueerd en toegewezen aanF2
veld vanC1
opnieuw afgestemd op de vorige stap
Ref-veiligheid
De verzamelingsexpressies ref safety section is van toepassing op de constructie van parameterverzamelingen wanneer API's worden aangeroepen in hun uitgebreide vorm.
Params-parameters zijn impliciet scoped
wanneer hun type een ref struct is. UnscopedRefAttribute kan worden gebruikt om dat te overschrijven.
Metagegevens
In metagegevens kunnen we niet-matrix-params
parameters markeren met System.ParamArrayAttribute
, omdat params
matrices vandaag worden gemarkeerd.
Het lijkt er echter op dat we veel veiliger zijn om een ander kenmerk te gebruiken voor niet-matrix params
parameters.
De huidige VB-compiler zal ze bijvoorbeeld niet kunnen verwerken als ze versierd zijn met ParamArrayAttribute
, noch in normale, noch in uitgebreide vorm. Daarom zal een toevoeging van de 'params'-modifier waarschijnlijk VB-consumenten breken, en zeer waarschijnlijk consumenten uit andere talen of toepassingen.
Aangezien niet-matrix-params
parameters worden gemarkeerd met een nieuwe System.Runtime.CompilerServices.ParamCollectionAttribute
.
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Parameter, Inherited = true, AllowMultiple = false)]
public sealed class ParamCollectionAttribute : Attribute
{
public ParamCollectionAttribute() { }
}
}
Deze sectie is beoordeeld op LDM en is goedgekeurd.
Open vragen
Stacktoewijzingen
Hier volgt een citaat uit https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#unresolved-questions: "Stack-toewijzingen voor enorme verzamelingen kunnen de stapel opblazen. 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." Het lijkt erop dat we de vragen in het kader van dit voorstel moeten beantwoorden.
[Opgelost] Impliciete scoped
parameters
Er was een suggestie dat, wanneer params
een ref struct
parameter wijzigt, deze moet worden beschouwd als gedeclareerd scoped
.
Het argument wordt gemaakt dat het aantal gevallen waarin u wilt dat de parameter binnen het bereik valt, vrijwel 100% is bij het bekijken van de BCL-zaken. In een paar gevallen die dat nodig hebben, kan de standaardwaarde worden overschreven met [UnscopedRef]
.
Het kan echter onwenselijk zijn om de standaardinstelling te wijzigen op basis van de aanwezigheid van params
modifier. Vooral dat in overschrijvingen/implementaties de params
-modifier niet hoeft overeen te komen.
Resolutie:
Parameters van params zijn impliciet afgebakend - https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-11-15.md#params-improvements.
[Opgelost] Overweeg het afdwingen van scoped
of params
bij overschrijvingen
We hebben eerder aangegeven dat params
parameters standaard moeten worden scoped
. Dit introduceert echter vreemd gedrag bij het overriden, vanwege onze bestaande regels voor het herformuleren van params
.
class Base
{
internal virtual Span<int> M1(scoped Span<int> s1, params Span<int> s2) => throw null!;
}
class Derived : Base
{
internal override Span<int> M1(Span<int> s1, // Error, missing `scoped` on override
Span<int> s2 // Proposal: Error: parameter must include either `params` or `scoped`
) => throw null!;
}
We hebben een verschil in gedrag tussen het dragen van de params
en het dragen van de scoped
over overrides hier: params
wordt impliciet overgenomen, samen met scoped
, terwijl scoped
op zichzelf niet impliciet wordt overgenomen en op elk niveau moet worden herhaald.
Voorstel: We moeten afdwingen dat overschrijvingen van params
parameters expliciet params
of scoped
moeten aangeven als de oorspronkelijke definitie een scoped
parameter is. Met andere woorden, s2
in Derived
moet params
, scoped
of beide hebben.
Resolutie:
We moeten expliciet scoped
of params
vermelden bij het overschrijven van een params
parameter wanneer een niet-params
parameter hiervoor vereist is - https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-21.md#params-and-scoped-across-overrides.
[Opgelost] Moet de aanwezigheid van vereiste leden de declaratie van params
parameter voorkomen?
Bekijk het volgende voorbeeld:
using System.Collections;
using System.Collections.Generic;
public class MyCollection1 : IEnumerable<long>
{
IEnumerator<long> IEnumerable<long>.GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
public void Add(long l) => throw null;
public required int F; // Collection has required member and constructor doesn't initialize it explicitly
}
class Program
{
static void Main()
{
Test(2, 3); // error CS9035: Required member 'MyCollection1.F' must be set in the object initializer or attribute constructor.
}
// Proposal: An error is reported for the parameter indicating that the constructor that is required
// to be available doesn't initialize required members. In other words, one is able
// to declare such a parameter under the specified conditions.
static void Test(params MyCollection1 a)
{
}
}
Resolutie:
We valideren required
leden tegen de constructor die wordt gebruikt om te bepalen of ze in aanmerking komen voor een params
parameter op de declaratiesite - https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-21.md#required-members-and-params-parameters.
Alternatieven
Er is een alternatief voorstel dat alleen params
uitbreidt voor ReadOnlySpan<T>
.
U kunt ook zeggen dat met verzameluitdrukkingen nu in de taal beschikbaar, er helemaal geen ondersteuning voor params
uitgebreid hoeft te worden. Voor elk verzamelingstype. Als u een API met een verzamelingstype wilt gebruiken, moet een ontwikkelaar gewoon twee tekens toevoegen, [
vóór de uitgebreide lijst met argumenten en ]
erna. Gezien dat het uitbreiden van params
ondersteuning een overkill kan zijn, met name dat andere talen het verbruik van niet-matrix params
parameters binnenkort waarschijnlijk niet ondersteunen.
Verwante voorstellen
- https://github.com/dotnet/csharplang/issues/1757
- https://github.com/dotnet/csharplang/blob/main/proposals/format.md#extending-params
Verwante ontwerpvergaderingen
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-11-15.md#params-improvements
- 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
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-29.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-31.md#params-collections-evaluation-orders
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-21.md#params-collections
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-04-22.md#effect-of-language-version-on-overload-resolution-in-presence-of-params-collections
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-04-24.md#adjust-dynamic-binding-rules-for-a-situation-of-a-single-applicable-candidate
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-05-01.md#adjust-binding-rules-in-the-presence-of-a-single-candidate
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-03.md#params-collections-and-dynamic
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-12.md#params-span-breaks
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-17.md#params-span-breaks
C# feature specifications