Delen via


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 matrixtypeT[], in welk geval het elementtype is T
  • Een span van het type
    • System.Span<T>
    • System.ReadOnlySpan<T>
      in welke gevallen het elementtype wordt T
  • 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 wordt T

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 modifiers in, outof ref. 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. Als A 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 Aeen 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, outof ref 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

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ᵥ naar Qᵥ niet beter dan de impliciete conversie van Eᵥ naar Pᵥ, en
  • voor ten minste één argument is de conversie van Eᵥ naar Pᵥ beter dan de conversie van Eᵥ naar Qᵥ.

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 en Mₑ een algemene methode is, is Mᵢ beter dan Mₑ.
  • Als Mᵢ in de normale vorm van toepassing is en Mₑ een paramsverzameling heeft en alleen van toepassing is in de uitgevouwen vorm, is Mᵢ beter dan Mₑ.
  • 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 van Mₑ, is Mᵢ beter dan Mₑ.
  • Als Mᵥ meer specifieke parametertypen heeft dan Mₓ, is Mᵥ beter dan Mₓ. Laat {R1, R2, ..., Rn} en {S1, S2, ..., Sn} de niet-geïnstantieerde en niet-uitgevouwen parametertypen van Mᵥ en Mₓvoorstellen. Mᵥparametertypen zijn specifieker dan Mₓs als voor elke parameter Rx niet minder specifiek is dan Sx, en voor ten minste één parameter is Rx specifieker dan Sx:
    • 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 in Mₓ, is Mᵥ beter dan Mₓ.
  • Als voor ten minste één parameter Mᵥ de betere keuze voor het doorgeven van parameters gebruikt (§12.6.4.4) dan de bijbehorende parameter in Mₓ en geen van de parameters in Mₓ de betere keuze voor parameterdoorgifte dan Mᵥ, is Mᵥ beter dan Mₓ.
  • Anders, als beide methoden parametersverzamelingen hebben en alleen van toepassing zijn in hun uitgebreide formulieren, is Mᵢ beter dan Mₑ 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 van Mₑ
    • verzameling parameters van Mᵢ is System.ReadOnlySpan<Eᵢ>, en verzameling parameters van Mₑ is System.Span<Eₑ>, en er bestaat een identiteitsconversie van Eᵢ naar Eₑ
    • verzameling parameters van Mᵢ is System.ReadOnlySpan<Eᵢ> of System.Span<Eᵢ>, en verzameling parameters van Mₑ is een array_or_array_interface__type met elementtypeEₑ, en er bestaat een identiteitsconversie van Eᵢ naar Eₑ
  • 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 van Mₑ

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:

  1. GetB wordt aangeroepen
  2. MyCollection wordt gemaakt en ingevuld en GetC wordt in het proces aangeroepen
  3. GetA wordt aangeroepen
  4. 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:

  1. GetA wordt aangeroepen en in de cache opgeslagen
  2. MyCollection wordt gemaakt, ingevuld en in de cache opgeslagen, wordt GetC in het proces aangeroepen
  3. De getter van de indexeerder wordt aangeroepen met waarden uit de cache voor indexen.
  4. Resultaat wordt verhoogd
  5. 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:

  1. GetA wordt aangeroepen en in de cache opgeslagen
  2. Er wordt een lege MyCollection gemaakt en in de cache opgeslagen
  3. De getter van de indexeerder wordt aangeroepen met gecachede waarden voor de indexen.
  4. Resultaat wordt verhoogd
  5. 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:

  1. GetA wordt aangeroepen en in de cache opgeslagen
  2. MyCollection wordt gemaakt, ingevuld en in de cache opgeslagen, wordt GetC in het proces aangeroepen
  3. De getter van de indexeerder wordt aangeroepen met gecachede waarden voor de indexen.
  4. GetF1 wordt geëvalueerd en toegewezen aan F1 veld van C1 opnieuw afgestemd op de vorige stap
  5. De getter van de indexeerder wordt aangeroepen met gecacheerde waarden voor indexen
  6. GetF2 wordt geëvalueerd en toegewezen aan F2 veld van C1 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:

  1. GetA wordt aangeroepen en in de cache opgeslagen
  2. GetC wordt aangeroepen en in de cache opgeslagen
  3. De getter van de indexeerfunctie wordt aangeroepen met GetA in de cache en een nieuwe matrix die is gevuld met GetC in de cache
  4. GetF1 wordt geëvalueerd en toegewezen aan het F1 veld van C1 dat is geretourneerd in de vorige stap
  5. De getter van de indexeerfunctie wordt aangeroepen met GetA in de cache en een nieuwe matrix die is gevuld met GetC in de cache
  6. GetF2 wordt geëvalueerd en toegewezen aan F2 veld van C1 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:

  1. GetA wordt aangeroepen en in de cache opgeslagen
  2. Er wordt een lege MyCollection gemaakt en in de cache opgeslagen
  3. De getter van de indexeerfunctie wordt aangeroepen met waarden in de cache voor indexen
  4. GetF1 wordt geëvalueerd en toegewezen aan F1 veld van C1 opnieuw afgestemd op de vorige stap
  5. De getter van de indexeerder wordt aangeroepen met gecachede waarden voor indexen.
  6. GetF2 wordt geëvalueerd en toegewezen aan F2 veld van C1 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, scopedof 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.