Dela via


params Collections

Notera

Den här artikeln är en funktionsspecifikation. Specifikationen fungerar som designdokument för funktionen. Den innehåller föreslagna specifikationsändringar, tillsammans med information som behövs under utformningen och utvecklingen av funktionen. Dessa artiklar publiceras tills de föreslagna specifikationsändringarna har slutförts och införlivats i den aktuella ECMA-specifikationen.

Det kan finnas vissa skillnader mellan funktionsspecifikationen och den slutförda implementeringen. Dessa skillnader samlas in i de relevanta LDM-anteckningar (Language Design Meeting).

Du kan läsa mer om processen för att införa funktionsspecifikationer i C#-språkstandarden i artikeln om specifikationerna.

Champion-problem: https://github.com/dotnet/csharplang/issues/7700

Sammanfattning

På C# 12-språket har stöd lagts till för att skapa instanser av samlingstyper utöver bara matriser. Se samlingsuttryck. Det här förslaget utökar params stöd för alla sådana samlingstyper.

Motivation

En params matrisparameter är ett bekvämt sätt att anropa en metod som tar en godtycklig längdlista med argument. I dag måste params parameter vara en matristyp. Det kan dock vara fördelaktigt för en utvecklare att kunna ha samma bekvämlighet när de anropar API:er som tar andra samlingstyper. Till exempel en ImmutableArray<T>, ReadOnlySpan<T>eller en vanlig IEnumerable. Särskilt i de fall då kompilatorn kan undvika en implicit matrisallokering i syfte att skapa samlingen (ImmutableArray<T>, ReadOnlySpan<T>osv.).

I situationer där ett API tar en samlingstyp lägger utvecklare i dag vanligtvis till en params överlagring som tar en matris, konstruerar målsamlingen och anropar den ursprungliga överlagringen med den samlingen, vilket innebär att API:ets konsumenter måste byta ut en extra matrisallokering för enkelhetens skull.

En annan motivation är möjligheten att lägga till en överbelastning av params-avsnitt och låta den få företräde framför matrisversionen, genom att bara kompilera om den befintliga källkoden.

Detaljerad design

Metodparametrar

Avsnittet -metodparametrar justeras enligt följande.

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
    ;

En parameter_collection består av en valfri uppsättning attribut, en params-modifierare, en valfri scoped-modifierare, en typoch en identifierare. En parametersamling deklarerar en enskild parameter av den angivna typen med det angivna namnet. Den typen för en parametersamling ska vara en av följande giltiga måltyper för ett samlingsuttryck (se https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#conversions):

  • En endimensionell matristypT[], där elementtypen är T
  • spännviddstyp
    • System.Span<T>
    • System.ReadOnlySpan<T>
      i vilka fall elementtypen är T
  • En typ med en lämplig skapa-metod som kan anropas utan ytterligare argument, som är minst lika tillgänglig som den deklarerande medlemmen, och med en motsvarande elementtyp som är resultatet av den bestämningen
  • En struct eller klasstyp som implementerar System.Collections.IEnumerable där:
    • Den typen har en konstruktor som kan anropas utan argument och konstruktorn är minst lika tillgänglig som den deklarerande medlemmen.

    • Typen har en instansmetod (inte ett tillägg) Add där:

      • Metoden kan anropas med ett argument med ett enda värde.
      • Om metoden är generisk kan typargumenten härledas från argumentet.
      • Metoden är minst lika tillgänglig som den deklarerande medlemmen.

      I vilket fall som helst är elementtypen iterationstypen av typ .

  • En gränssnittstyp
    • 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>
      i vilka fall elementtypen är T

I en metodanrop tillåter en parametersamling antingen att ett enda argument av den angivna parametertypen anges, eller att noll eller fler argument av samlingens elementtyp anges. Parametersamlingar beskrivs ytterligare i Parametersamlingar.

En parameter_collection kan inträffa efter en valfri parameter, men kan inte ha ett standardvärde – utelämnandet av argument för en parameter_collection skulle i stället leda till att en tom samling skapas.

Parametersamlingar

Avsnittet Parametermatriser ändras och justeras enligt följande.

En parameter som deklareras med en params modifierare är en parametersamling. Om en formell parameterlista innehåller en parametersamling ska den vara den sista parametern i listan och den ska vara av den typ som anges i metodparametrar avsnittet.

Obs: Det går inte att kombinera params-modifieraren med modifierarna in, outeller ref. slutkommentar

En parametersamling tillåter att argument anges på ett av två sätt i ett metodanrop:

  • Argumentet som anges för en parametersamling kan vara ett enda uttryck som implicit kan konverteras till parametersamlingstypen. I det här fallet fungerar parametersamlingen exakt som en värdeparameter.
  • Alternativt kan anropet ange noll eller fler argument för parametersamlingen, där varje argument är ett uttryck som implicit kan konverteras till parametersamlingens elementtyp. I det här fallet skapar anropet en instans av parametersamlingstypen enligt de regler som anges i Samlingsuttryck som om argumenten användes som uttryckselement i ett samlingsuttryck i samma ordning och använder den nyligen skapade samlingsinstansen som det faktiska argumentet. När du skapar samlingsinstansen används de ursprungliga okonverterade argumenten.

Förutom att tillåta ett variabelt antal argument i ett anrop motsvarar en parametersamling exakt en värdeparameter av samma typ.

När du utför överbelastningsmatchning kan en metod med en parametersamling vara tillämplig, antingen i sin normala form eller i dess expanderade form. Den expanderade formen av en metod är endast tillgänglig om metodens normala form inte är tillämplig och endast om en tillämplig metod med samma signatur som det expanderade formuläret inte redan har deklarerats i samma typ.

En potentiell tvetydighet uppstår mellan det normala formuläret och metodens expanderade form med ett argument för en enskild parametersamling när den kan användas som själva parametersamlingen och som elementet i parametersamlingen samtidigt. Tvetydigheten utgör dock inga problem, eftersom den kan lösas genom att infoga en typkonvertering eller använda ett samlingsuttryck, om det behövs.

Signaturer och överlagring

Alla regler kring params modifierare i Signaturer och överlagring av förblir som de är.

Tillämplig funktionsmedlem

Avsnittet Tillämpbar funktionsmedlem justeras på följande sätt.

Om en funktionsmedlem som innehåller en parametersamling inte är tillämplig i dess normala form kan funktionsmedlemmen i stället vara tillämplig i dess expanderade formulär:

  • Om parametersamlingen inte är en matris gäller inte ett expanderat formulär för språkversionerna C# 12 och senare.
  • Det expanderade formuläret skapas genom att parametersamlingen i funktionsmedlemsdeklarationen ersätts med noll eller fler värdeparametrar för parametersamlingens elementtyp så att antalet argument i argumentlistan A matchar det totala antalet parametrar. Om A har färre argument än antalet fasta parametrar i funktionsmedlemsdeklarationen kan inte funktionsmedlemmens utökade form konstrueras och är därför inte tillämplig.
  • I annat fall gäller det expanderade formuläret om något av följande gäller för varje argument i A:
    • parameteröverföringsläget för argumentet är identiskt med parameteröverföringsläget för motsvarande parameter, och
      • för en parameter med fast värde eller en värdeparameter som skapats av expansionen, finns det en implicit konvertering från argumentuttrycket till typen av motsvarande parameter, eller
      • för parametern in, outeller ref är typen av argumentuttryck identisk med typen av motsvarande parameter.
    • parameteröverföringsläget för argumentet är värde och parameteröverföringsläget för motsvarande parameter är indata, och det finns en implicit konvertering från argumentuttrycket till typen av motsvarande parameter

Bättre funktionsmedlem

Avsnittet Better function member justeras enligt följande.

Med tanke på en argumentlista A med en uppsättning argumentuttryck {E₁, E₂, ..., Eᵥ} och två tillämpliga funktionsmedlemmar Mᵥ och Mₓ med parametertyper {P₁, P₂, ..., Pᵥ} och {Q₁, Q₂, ..., Qᵥ}, definieras Mᵥ som en bättre funktionsmedlem än Mₓ om

  • för varje argument är den implicita konverteringen från Eᵥ till Qᵥ inte bättre än den implicita konverteringen från Eᵥ till Pᵥ, och
  • för minst ett argument är konverteringen från Eᵥ till Pᵥ bättre än konverteringen från Eᵥ till Qᵥ.

Om parametertypsekvenserna {P₁, P₂, ..., Pᵥ} och {Q₁, Q₂, ..., Qᵥ} är likvärdiga (d.v.s. varje Pᵢ har en identitetskonvertering till motsvarande Qᵢ) tillämpas följande regler för att fastställa den bättre funktionsmedlemmen.

  • Om Mᵢ är en icke-generisk metod och Mₑ är en allmän metod är Mᵢ bättre än Mₑ.
  • Annars, om Mᵢ är tillämpligt i sin normala form och Mₑ har en params-samling och endast är tillämplig i dess expanderade form, är Mᵢ bättre än Mₑ.
  • Annars, om båda metoderna har params-samlingar och endast är tillämpliga i deras expanderade formulär, och om params-samlingen av Mᵢ har färre element än params-samlingen av Mₑ, är Mᵢ bättre än Mₑ.
  • Om Mᵥ annars har mer specifika parametertyper än Mₓär Mᵥ bättre än Mₓ. Låt {R1, R2, ..., Rn} och {S1, S2, ..., Sn} representera de oinstifierade och oexpandererade parametertyperna för Mᵥ och Mₓ. Mᵥparametertyper är mer specifika än Mₓom Rx för varje parameter inte är mindre specifik än Sxoch för minst en parameter är Rx mer specifik än Sx:
    • En typparameter är mindre specifik än en icke-typparameter.
    • Rekursivt är en konstruerad typ mer specifik än en annan konstruerad typ (med samma antal typargument) om minst ett typargument är mer specifikt och inget typargument är mindre specifikt än motsvarande typargument i det andra.
    • En matristyp är mer specifik än en annan matristyp (med samma antal dimensioner) om elementtypen för den första är mer specifik än elementtypen för den andra.
  • Om en medlem är en icke-lyftad operatör och den andra är en lyftad operatör, så är den icke-lyftade bättre.
  • Om ingen funktionsmedlem visade sig vara bättre och alla parametrar i Mᵥ har ett motsvarande argument medan standardargument måste ersättas med minst en valfri parameter i Mₓär Mᵥ bättre än Mₓ.
  • Om Mᵥ för minst en parameter använder bättre parameteröverföringsalternativ (§12.6.4.4) än motsvarande parameter i Mₓ och ingen av parametrarna i Mₓ använda det bättre parameteröverföringsalternativet än Mᵥär Mᵥ bättre än Mₓ.
  • Annars, om båda metoderna har params-samlingar och endast är tillämpliga i deras expanderade formulär är Mᵢ bättre än Mₑ om samma uppsättning argument motsvarar samlingselementen för båda metoderna, och något av följande gäller (detta motsvarar https://github.com/dotnet/csharplang/blob/main/proposals/csharp-13.0/collection-expressions-better-conversion.md):
    • båda params-samlingarna är inte span_types, och en implicit konvertering från params-samlingen av Mᵢ till params-samlingen av Mₑ finns
    • params-samling av Mᵢ är System.ReadOnlySpan<Eᵢ>, och params-samling av Mₑ är System.Span<Eₑ>, och det finns en identitetskonvertering från Eᵢ till Eₑ
    • params-samling av Mᵢ är System.ReadOnlySpan<Eᵢ> eller System.Span<Eᵢ>, och params-samling av Mₑ är en (array- eller array-interface-typ) med elementtypEₑ, och det finns en identitetskonvertering från Eᵢ till Eₑ
  • Annars är ingen funktionsmedlem bättre.

Anledningen till att den nya regeln för bindningsbrott placeras i slutet av listan är det sista underobjektet

  • båda params-samlingarna är inte span_types, och en implicit konvertering från params-samlingen av Mᵢ till params-samlingen av Mₑ finns

Det är tillämpligt för matriser och därför introducerar utförandet av tie-break tidigare en beteendeändring för befintliga scenarier.

Till exempel:

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 {}

Om någon av de tidigare utsorteringsreglerna gäller (inklusive regeln "bättre argumentkonverteringar") kan resultatet av överbelastningslösningen skilja sig från fallet när ett explicit samlingsuttryck används som argument i stället.

Till exempel:

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) {}
}

Vårt främsta problem är dock scenarier där överlagringar endast skiljer sig efter typ av params-samling, men samlingstyperna har samma elementtyp. Beteendet bör vara konsekvent med tydliga samlingsuttryck för dessa fall.

Villkoret "om samma uppsättning argument motsvarar samlingselementen för båda metoderna" är viktigt för scenarier som:

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) {}
}

Det känns inte rimligt att "jämföra" samlingar som är byggda från olika element.

Det här avsnittet granskades på LDM- och godkändes.

En effekt av dessa regler är att när params av olika elementtyper exponeras blir dessa tvetydiga när de anropas med en tom argumentlista. Till exempel:

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) {
    }
}

Med tanke på att vi prioriterar elementtyp framför allt annat verkar detta rimligt; Det finns inget som talar om för språket om användaren föredrar int? framför int i det här scenariot.

Dynamisk bindning

Utökade former av kandidater som använder icke-matrisparametrar-samlingar kommer inte att betraktas som giltiga kandidater av den aktuella C#-körningsbindaren.

Om primary_expression inte har kompileringstidstyp dynamicgenomgår metodanropet en begränsad kompileringstidskontroll enligt beskrivningen i §12.6.5 Kompileringstidskontroll av dynamiskt medlemsanrop.

Om endast en enskild kandidat klarar testet är anropet av kandidaten statiskt bundet när alla följande villkor uppfylls:

  • kandidaten är en lokal funktion
  • Kandidaten är antingen inte generisk eller så anges dess typargument uttryckligen.
  • Det finns ingen tvetydighet mellan normala och utökade former av kandidaten som inte kan lösas vid kompileringstillfället.

Annars är invocation_expression dynamiskt bunden.

Om endast en enskild kandidat klarade testet ovan:

  • Om den kandidaten är en lokal funktion uppstår ett kompileringsfel.
  • Om den kandidaten endast är tillämplig i expanderad form med hjälp av paramssamlingar som inte är matris, uppstår ett kompileringsfel.

Vi bör också överväga att återställa/åtgärda specifikationsöverträdelser som påverkar lokala funktioner i dag, se https://github.com/dotnet/roslyn/issues/71399.

LDM har bekräftat att vi vill åtgärda denna specifikationsöverträdelse.

Uttrycksträd

Samlingsuttryck stöds inte i uttrycksträd. På samma sätt stöds inte expanderade former av icke-matrisparamssamlingar i uttrycksträd. Vi kommer inte att ändra hur kompilatorn binder lambdas för uttrycksträd med målet att undvika användning av API:er som använder utökade former av params-samlingar som inte är matriser.

Utvärderingsordning med icke-matrissamlingar i icke-triviala scenarier

Det här avsnittet granskades på LDM- och godkändes. Trots att matrisfall avviker från andra samlingar behöver den officiella språkspecifikationen inte ange olika regler för matriser. Avvikelserna kan helt enkelt behandlas som en implementeringsartefakt. Samtidigt har vi inte för avsikt att ändra det befintliga beteendet kring matriser.

Namngivna argument

En samlingsinstans skapas och fylls i efter att det lexikalt föregående argumentet har utvärderats, men innan det lexikaliskt följande argumentet utvärderas.

Till exempel:

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;
}

Utvärderingsordningen är följande:

  1. GetB kallas
  2. MyCollection skapas och fylls, och GetC kallas i processen
  3. GetA kallas
  4. Test kallas

Observera att matrisen i params-matrisen skapas precis innan målmetoden anropas, efter att alla argument har utvärderats i lexikal ordning.

Sammansatt tilldelning

En samlingsinstans skapas och fylls i efter att det lexikalt tidigare indexet har utvärderats, men innan det lexikaliskt följande index utvärderas. Instansen används för att anropa getter och setter för målindexeraren.

Till exempel:

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;
}

Utvärderingsordningen är följande:

  1. GetA anropas och cachelagras
  2. MyCollection skapas, fylls i och cachelagras, GetC anropas i processen
  3. Indexerarens getter anropas med cachelagrade värden för index
  4. Resultatet ökas
  5. Indexerarens setter anropas med cachelagrade värden för index och resultatet av inkrementet

Ett exempel med en tom samling:

class Program
{
    static void Test(Program p)
    {
        p[GetA()]++;
    }

    int this[int a, params MyCollection c] { get => 0; set {} }

    static int GetA() => 0;
}

Utvärderingsordningen är följande:

  1. GetA anropas och cachelagras
  2. En tom MyCollection skapas och cachelagras
  3. Indexerarens getter anropas med cachelagrade värden för index
  4. Resultatet ökas
  5. Indexerarens setter anropas med cachelagrade värden för index och resultatet av inkrementet

Objektinitierare

En samlingsinstans skapas och fylls i efter att det lexikalt tidigare indexet har utvärderats, men innan det lexikaliskt följande index utvärderas. Instansen används för att anropa indexerarens getter så många gånger som behövs, om det finns några.

Till exempel:

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;
}

Utvärderingsordningen är följande:

  1. GetA anropas och cachelagras
  2. MyCollection skapas, fylls i och cachelagras, GetC anropas i processen
  3. Indexerarens getter anropas med cachelagrade värden för index
  4. GetF1 utvärderas och tilldelas F1-fältet på C1 som returnerades i föregående steg
  5. Indexerarens getter anropas med cachelagrade värden för index
  6. GetF2 utvärderas och tilldelas F2-fältet på C1 som returnerades i föregående steg

Observera att vid användning av params-arrayen utvärderas och cachelagras dess element, men en ny instans av en array (med samma värden inuti) används för varje anrop av indexerarens getter istället. I exemplet ovan är utvärderingsordningen följande:

  1. GetA anropas och cachelagras
  2. GetC anropas och cachelagras
  3. Indexerarens get-funktion anropas med cachad GetA och ny array ifylld med cachad GetC
  4. GetF1 utvärderas och tilldelas F1-fältet på C1 som returnerades i föregående steg
  5. Indexerarens get-funktion anropas med cachad GetA och ny array ifylld med cachad GetC
  6. GetF2 utvärderas och tilldelas F2-fältet på C1 som returnerades i föregående steg

Ett exempel med en tom samling:

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;
}

Utvärderingsordningen är följande:

  1. GetA anropas och cachelagras
  2. En tom MyCollection skapas och cachelagras
  3. Indexerarens getter anropas med cachelagrade värden för index
  4. GetF1 utvärderas och tilldelas F1-fältet på C1 som returnerades i föregående steg
  5. Indexerarens getter anropas med cachelagrade värden för index
  6. GetF2 utvärderas och tilldelas F2-fältet på C1 som returnerades i föregående steg

Refsäkerhet

Avsnittet om referenssäkerhet för samlingsuttryck gäller för konstruktionen av parametersamlingar när API:erna anropas i utökat format.

Params-parametrar är implicit scoped när deras typ är en ref-struktur. UnscopedRefAttribute kan användas för att åsidosätta detta.

Metadata

I metadata kan vi markera parametrar som inte är matriser params med System.ParamArrayAttribute, eftersom params matriser markeras idag. Det verkar dock vara mycket säkrare att använda ett annat attribut för params parametrar som inte är matrisparametrar. Den aktuella VB-kompilatorn kan till exempel inte använda dem dekorerade med ParamArrayAttribute varken normalt eller i expanderad form. Därför kan ett tillägg av modifieraren "params" sannolikt orsaka problem för VB-användare och troligen även för användare av andra språk eller verktyg.

Med tanke på detta markeras icke-matrisparametrar params med en ny System.Runtime.CompilerServices.ParamCollectionAttribute.

namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Parameter, Inherited = true, AllowMultiple = false)]
    public sealed class ParamCollectionAttribute : Attribute
    {
        public ParamCollectionAttribute() { }
    }
}

Det här avsnittet granskades på LDM- och godkändes.

Öppna frågor

Stackallokering

Här är ett citat från https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#unresolved-questions: "Stackallokeringar för stora samlingar kan orsaka stackkrascher. Ska kompilatorn ha en heuristik för att placera denna data på heap? Bör språket vara ospecificerat för att möjliggöra den här flexibiliteten? Vi bör följa specifikationen för params Span<T>. Det låter som om vi måste besvara frågorna i samband med detta förslag.

[Löst] Implicit scoped parametrar

Det fanns ett förslag om att när params ändrar en ref struct parameter bör den betraktas som deklarerad scoped. Det hävdas att antalet fall där man vill att parametern ska begränsas är i stort sett 100% när man granskar BCL-fallen. I några fall som behöver det kan standardvärdet skrivas över med [UnscopedRef].

Det kan dock vara oönskat att ändra standardinställningen helt enkelt baserat på förekomsten av params modifierare. Särskilt i scenarier med åsidosättning eller implementering behöver inte params-modifieraren matcha.

Upplösning:

Parametrar för params är implicit avgränsade – https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-11-15.md#params-improvements.

[Löst] Överväg att tillämpa scoped eller params genom åsidosättningar

Vi har tidigare sagt att params parametrar ska vara scoped som standard. Detta introducerar dock udda beteende vid åsidosättande, på grund av våra befintliga regler kring omformulering av 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!;
}

Vi har en skillnad i beteende mellan att använda params och att använda scoped över åsidosättningar här: params ärvs implicit och med det scoped, medan scoped på egen hand inte är ärvt implicit och måste upprepas på alla nivåer.

Förslag: Vi bör framtvinga att åsidosättningar av params parametrar uttryckligen måste ange params eller scoped om den ursprungliga definitionen är en scoped parameter. Med andra ord måste s2 i Derived ha params, scopedeller båda.

Upplösning:

Vi kräver att du uttryckligen anger scoped eller params vid åsidosättning av en params parameter när en parameter som inte ärparams skulle krävas för att göra det – https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-21.md#params-and-scoped-across-overrides.

[Löst] Bör närvaro av nödvändiga medlemmar förhindra deklaration av params parameter?

Tänk på följande exempel:

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)
    {
    }
}

Upplösning:

Vi verifierar medlemmarna i required gentemot konstruktorn som används för att avgöra om de är berättigade att vara en params-parameter vid deklarationsstället – https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-21.md#required-members-and-params-parameters.

Alternativ

Det finns ett alternativt förslag som endast utökar params för ReadOnlySpan<T>.

Man kan också säga att med samlingsuttryck nu i språket, finns det inget behov av att utöka params stöd alls. För alla samlingstyper. För att använda ett API med samlingstyp behöver en utvecklare helt enkelt lägga till två tecken, [ före den expanderade listan med argument och ] efter det. Med tanke på detta kan det vara en överdrift att utöka stödet för params, särskilt som andra språk sannolikt inte kommer att stödja användning av icke-matris params parametrar någon gång snart.