Condividi tramite


params Collections

Nota

Questo articolo è una specifica di funzionalità. La specifica funge da documento di progettazione per la funzionalità. Include le modifiche specifiche proposte, insieme alle informazioni necessarie durante la progettazione e lo sviluppo della funzionalità. Questi articoli vengono pubblicati fino a quando le modifiche specifiche proposte non vengono completate e incorporate nella specifica ECMA corrente.

Potrebbero verificarsi alcune discrepanze tra la specifica di funzionalità e l'implementazione completata. Tali differenze vengono acquisite nelle pertinenti note del language design meeting (LDM) .

Altre informazioni sul processo per l'adozione di speclet di funzionalità nello standard del linguaggio C# sono disponibili nell'articolo sulle specifiche di .

Problema del campione: https://github.com/dotnet/csharplang/issues/7700

Sommario

Nel linguaggio C# 12 è stato aggiunto il supporto per la creazione di istanze di tipi di raccolta oltre a matrici. Consulta le espressioni della collezione . Questa proposta estende il supporto params a tutti tali tipi di raccolta.

Motivazione

Un parametro di matrice params consente di chiamare un metodo che accetta un elenco arbitrario di argomenti. Oggi il parametro params deve essere un tipo di array. Tuttavia, può essere utile per uno sviluppatore avere la stessa comodità quando si chiamano API che accettano altri tipi di raccolta. Ad esempio, un ImmutableArray<T>, un ReadOnlySpan<T>o un IEnumerablenormale. In particolare nei casi in cui il compilatore è in grado di evitare un'allocazione implicita della matrice allo scopo di creare la raccolta (ImmutableArray<T>, ReadOnlySpan<T>e così via).

Attualmente, in situazioni in cui un'API accetta un tipo di raccolta, gli sviluppatori in genere aggiungono un overload params che accetta una matrice, costruiscono la raccolta di destinazione e chiamano l'overload originale con tale raccolta, quindi i consumer dell'API devono scambiare un'allocazione di matrici aggiuntiva per praticità.

Un'altra motivazione è la possibilità di aggiungere un overload di intervalli di parametri e avere la precedenza sulla versione della matrice, semplicemente ricompilando il codice sorgente esistente.

Progettazione dettagliata

Parametri del metodo

La sezione parametri del metodo è stata modificata come segue.

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
    ;

Un parameter_collection è costituito da un set facoltativo di attributi , un modificatore params, un modificatore facoltativo scoped, un tipo e un identificatore . Una raccolta di parametri dichiara un singolo parametro del tipo specificato con il nome specificato. Il tipo di una raccolta di parametri deve essere uno dei tipi di destinazione validi seguenti per un'espressione di raccolta (vedere https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#conversions):

  • Un array unidimensionale di tipo T[], nel qual caso il tipo di elemento dell'array è T
  • Tipo di intervallo
    • System.Span<T>
    • System.ReadOnlySpan<T>
      nei casi in cui il tipo di elemento è T
  • Un tipo con un metodo di creazione appropriato, che sia almeno altrettanto accessibile quanto il membro dichiarante, e con un tipo di elemento corrispondente risultante da tale determinazione.
  • un tipo di classe struct o che implementa dove:
    • Il tipo ha un costruttore che può essere richiamato senza argomenti, e la sua accessibilità è almeno pari a quella del membro dichiarante.

    • Il tipo dispone di un metodo di istanza (non di estensione) Add dove:

      • Il metodo può essere richiamato con un singolo argomento di valore.
      • Se il metodo è generico, gli argomenti di tipo possono essere dedotti dal parametro.
      • Il metodo è accessibile almeno quanto il membro dichiarante.

      Nel qual caso il tipo di elemento è il tipo di iterazione del tipo .

  • Tipo di interfaccia
    • 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>
      nei casi in cui il tipo di elemento è T

In una chiamata al metodo, una raccolta di parametri consente di specificare un singolo argomento del tipo di parametro specificato oppure consente di specificare zero o più argomenti del tipo di elemento della raccolta. Le raccolte di parametri sono descritte più avanti in raccolte di parametri.

Un parameter_collection può essere presente dopo un parametro facoltativo, ma non può avere un valore predefinito. L'omissione di argomenti per il parameter_collection comporta altrimenti la creazione di una raccolta vuota.

Raccolte di parametri

La sezione matrici di parametri è stata rinominata e modificata come segue.

Un parametro dichiarato con un modificatore params è una raccolta di parametri. Se un elenco di parametri formali include una raccolta di parametri, deve essere l'ultimo parametro nell'elenco e deve essere di tipo specificato nella sezione Parametri del metodo sezione.

Nota: non è possibile combinare il modificatore params con i modificatori in, outo ref. nota finale

Una raccolta di parametri consente di specificare gli argomenti in uno dei due modi in una chiamata al metodo:

  • L'argomento specificato per una raccolta di parametri può essere una singola espressione convertibile in modo implicito nel tipo di raccolta di parametri. In questo caso, la collezione di parametri agisce esattamente come un parametro di valore.
  • In alternativa, la chiamata può specificare zero o più argomenti per la raccolta di parametri, dove ogni argomento è un'espressione convertibile in modo implicito nel tipo di elemento della raccolta di parametri. In questo caso, la chiamata crea un'istanza del tipo di raccolta di parametri in base alle regole specificate nelle espressioni di raccolta come se gli argomenti fossero utilizzati come elementi di espressione in un'espressione di raccolta nello stesso ordine ed essa usa l'istanza della raccolta appena creata come argomento reale. Quando si costruisce l'istanza della raccolta, vengono utilizzati gli argomenti non convertiti originali.

Tranne per consentire un numero variabile di argomenti in un'invocazione, una raccolta di parametri è esattamente equivalente a un parametro di valore dello stesso tipo.

Quando si esegue la risoluzione dell'overload, un metodo con una raccolta di parametri potrebbe essere applicabile, nella forma normale o nel formato espanso. La forma espansa di un metodo è disponibile solo se la forma normale del metodo non è applicabile e solo se un metodo applicabile con la stessa firma del modulo espanso non è già dichiarato nello stesso tipo.

Una potenziale ambiguità si verifica tra la forma normale e la forma espansa del metodo con un singolo argomento di raccolta di parametri quando può essere utilizzato sia come collezione di parametri sia come elemento della collezione di parametri allo stesso tempo. L'ambiguità non presenta alcun problema, tuttavia, dato che può essere risolta inserendo un cast o usando un'espressione di raccolta, se necessario.

Firme e sovraccarichi

Tutte le regole relative al modificatore params nelle firme e nell'overloading rimangono invariate.

Membro della funzione applicabile

La sezione membro della funzione applicabile viene modificata come indicato di seguito.

Se un membro della funzione che include una raccolta di parametri non è applicabile nel formato normale, il membro della funzione potrebbe invece essere applicabile nel relativo modulo espanso:

  • Se la raccolta di parametri non è una matrice, un modulo espanso non è applicabile per le versioni del linguaggio C# 12 e successive.
  • Il modulo espanso viene costruito sostituendo, nella dichiarazione del membro della funzione, la raccolta di parametri con zero o più parametri di valore del tipo di elemento della raccolta di parametri, in modo che il numero di argomenti nell'elenco di argomenti A corrisponda al numero totale di parametri. Se A ha meno argomenti rispetto al numero di parametri fissi nella dichiarazione del membro della funzione, la forma espansa del membro della funzione non può essere costruita e pertanto non è applicabile.
  • In caso contrario, il modulo espanso è applicabile se per ogni argomento in A, uno dei seguenti è vero:
    • la modalità di passaggio dei parametri dell'argomento è identica a quella del parametro corrispondente e
      • per un parametro di valore fisso o un parametro di valore creato dall'espansione, esiste una conversione implicita dall'espressione dell'argomento al tipo del parametro corrispondente oppure
      • per un parametro in, outo ref, il tipo dell'espressione dell'argomento è identico al tipo del parametro corrispondente.
    • la modalità di passaggio del parametro dell'argomento è valore, e la modalità di passaggio del parametro corrispondente è input, ed esiste una conversione implicita dall'espressione dell'argomento al tipo del parametro corrispondente

Membro di funzione migliore

La sezione relativa al membro della funzione Better , identificata con, viene modificata come segue.

Dato un elenco di argomenti A con un set di espressioni di argomento {E₁, E₂, ..., Eᵥ} e due membri di funzione applicabili Mᵥ e Mₓ con tipi di parametro {P₁, P₂, ..., Pᵥ} e {Q₁, Q₂, ..., Qᵥ}, Mᵥ è definito come membro di funzione migliore rispetto a Mₓ se

  • per ogni argomento, la conversione implicita da Eᵥ a Qᵥ non è migliore della conversione implicita da Eᵥ a Pᵥe
  • per almeno un argomento, la conversione da Eᵥ a Pᵥ è migliore rispetto alla conversione da Eᵥ a Qᵥ.

Nel caso in cui le sequenze di tipi di parametro {P₁, P₂, ..., Pᵥ} e {Q₁, Q₂, ..., Qᵥ} siano equivalenti (ad esempio, ogni Pᵢ ha una conversione di identità nell'Qᵢcorrispondente ), vengono applicate le regole di interruzione di associazione seguenti, per determinare il membro della funzione migliore.

  • Se Mᵢ è un metodo non generico e Mₑ è un metodo generico, Mᵢ è migliore di Mₑ.
  • In caso contrario, se Mᵢ è applicabile nella forma normale e Mₑ ha una raccolta di parametri ed è applicabile solo nel formato espanso, Mᵢ è migliore di Mₑ.
  • In caso contrario, se entrambi i metodi dispongono di raccolte di parametri e sono applicabili solo nei relativi moduli espansi e se l'insieme params di Mᵢ ha meno elementi rispetto all'insieme params di Mₑ, Mᵢ è migliore di Mₑ.
  • In caso contrario, se Mᵥ ha tipi di parametri più specifici di Mₓ, Mᵥ è migliore di Mₓ. Lasciare che {R1, R2, ..., Rn} e {S1, S2, ..., Sn} rappresentino i tipi di parametro non istanziati e non espansi di Mᵥ e Mₓ. i tipi di parametro di Mᵥsono più specifici di Mₓse, per ogni parametro, Rx non è meno specifico di Sxe, per almeno un parametro, Rx è più specifico di Sx:
    • Un parametro di tipo è meno specifico di un parametro non di tipo.
    • In modo ricorsivo, un tipo costruito è più specifico di un altro tipo costruito (con lo stesso numero di argomenti di tipo) se almeno un argomento di tipo è più specifico e nessun argomento di tipo è meno specifico dell'argomento di tipo corrispondente nell'altro.
    • Un tipo di matrice è più specifico di un altro tipo di matrice (con lo stesso numero di dimensioni) se il tipo di elemento del primo è più specifico del tipo di elemento del secondo.
  • In caso contrario, se un membro è un operatore non sollevato e l'altro è un operatore sollevato, quello non sollevato è migliore.
  • Se nessun membro della funzione è stato trovato migliore e tutti i parametri di Mᵥ hanno un argomento corrispondente, mentre gli argomenti predefiniti devono essere sostituiti per almeno un parametro facoltativo in Mₓ, Mᵥ è migliore di Mₓ.
  • Se per almeno un parametro Mᵥ usa la scelta migliore per il passaggio di parametri (§12.6.4.4) rispetto al parametro corrispondente in Mₓ e nessuno dei parametri in Mₓ usa una scelta di passaggio dei parametri migliore di Mᵥ, Mᵥ è migliore di Mₓ.
  • In caso contrario, se entrambi i metodi dispongono di raccolte di parametri e sono applicabili solo nelle loro forme espanse, Mᵢ è preferibile a Mₑ se lo stesso set di argomenti corrisponde agli elementi della raccolta per entrambi i metodi e si verifica una delle seguenti condizioni (corrisponde a https://github.com/dotnet/csharplang/blob/main/proposals/csharp-13.0/collection-expressions-better-conversion.md):
    • entrambe le raccolte params non sono span_type, ed esiste una conversione implicita dalla raccolta params di Mᵢ alla raccolta params di Mₑ
    • la raccolta di parametri di Mᵢ è System.ReadOnlySpan<Eᵢ>, e la raccolta di parametri di Mₑ è System.Span<Eₑ>, ed esiste una conversione di identità da Eᵢ a Eₑ
    • raccolta di parametri di Mᵢ è System.ReadOnlySpan<Eᵢ> o System.Span<Eᵢ>e la raccolta di parametri di Mₑ è un array_or_array_interface__type con tipo di elemento Eₑed esiste una conversione di identità da Eᵢ a Eₑ
  • In caso contrario, nessun membro della funzione è migliore.

Il motivo per cui la nuova regola del tie-break è posizionata alla fine dell'elenco è l'ultimo sottoelemento.

  • entrambe le raccolte params non sono span_type, ed esiste una conversione implicita dalla raccolta params di Mᵢ alla raccolta params di Mₑ

è applicabile alle matrici e, pertanto, l'esecuzione del tie-break in precedenza introdurrà una modifica del comportamento per gli scenari esistenti.

Per esempio:

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

Se una delle regole per risolvere le ambiguità precedenti si applica (inclusa la regola delle "conversioni di argomenti migliori"), il risultato della risoluzione dell'overload può essere diverso rispetto al caso in cui si usi un'espressione di collezione esplicita come argomento.

Per esempio:

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

Tuttavia, il problema principale riguarda gli scenari in cui le sovraccaricature differiscono solo per il tipo di raccolta dei parametri, ma i tipi di raccolta hanno lo stesso tipo di elemento. Il comportamento deve essere coerente con le espressioni di raccolta esplicite per questi casi.

La condizione "se lo stesso insieme di argomenti corrisponde agli elementi della collezione per entrambi i metodi" è importante per scenari come:

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

Non è ragionevole confrontare le raccolte create da elementi diversi.

Questa sezione è stata esaminata in LDM ed è stata approvata.

Un effetto di queste regole è che quando params di tipi di elementi diversi vengono esposti, questi saranno ambigui quando vengono chiamati con un elenco di argomenti vuoto. Per esempio:

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

Dato che diamo priorità al tipo di elemento rispetto a tutto il resto, questo sembra ragionevole; non c'è alcun criterio per indicare alla lingua se l'utente preferirebbe int? piuttosto che int in questo scenario.

Collegamento dinamico

Le forme espanse dei candidati che utilizzano raccolte di parametri non a matrice non verranno considerate candidati validi dallo strumento di associazione del runtime C# corrente.

Se il primary_expression non dispone di un tipo in fase di compilazione dynamic, la chiamata al metodo viene sottoposta a un controllo in fase di compilazione limitato, come descritto in §12.6.5 Controllo in fase di compilazione della chiamata dinamica dei membri.

Se un singolo candidato soddisfa il test, la chiamata del candidato viene associata in modo statico quando vengono soddisfatte tutte le condizioni seguenti:

  • il candidato è una funzione locale
  • il candidato non è generico, oppure i suoi argomenti di tipo sono specificati in modo esplicito;
  • non ci sono ambiguità tra le forme normali ed espanse del candidato che non possano essere risolte in fase di compilazione.

In caso contrario, l'espressione di invocazione è associata dinamicamente.

Se solo un singolo candidato ha superato il test precedente:

  • se tale candidato è una funzione locale, si verifica un errore in fase di compilazione;
  • Se tale candidato è applicabile soltanto in forma espansa utilizzando raccolte di parametri non in array, si verifica un errore durante la fase di compilazione.

Dovremmo anche considerare di annullare/correggere la violazione delle specifiche tecniche che influisce sulle funzioni locali, consultare https://github.com/dotnet/roslyn/issues/71399.

LDM ha confermato che vogliamo correggere questa violazione delle specifiche.

Alberi delle espressioni

Le espressioni di raccolta non sono supportate negli alberi di espressioni. Analogamente, le forme espanse di raccolte di parametri non-array non saranno supportate negli alberi di espressioni. Non verrà modificato il modo in cui il compilatore associa le lambda per gli alberi delle espressioni con l'obiettivo di evitare l'uso di API che utilizzano moduli espansi di collezioni di parametri non di array.

Ordine di valutazione con raccolte non di matrici in scenari non semplici

Questa sezione è stata esaminata in LDM ed è stata approvata. Nonostante il fatto che i casi degli array differiscano dalle altre raccolte, la specifica ufficiale del linguaggio non deve specificare regole diverse per gli array. Le deviazioni possono essere semplicemente considerate come artefatti di implementazione. Allo stesso tempo non si intende modificare il comportamento esistente intorno alle matrici.

Argomenti denominati

Viene creata e popolata un'istanza della raccolta dopo la valutazione lessicale dell'argomento precedente, ma prima che venga valutato l'argomento lessicalmente seguente.

Per esempio:

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

L'ordine di valutazione è il seguente:

  1. GetB viene chiamato
  2. MyCollection viene creato e popolato, GetC viene chiamato durante il processo
  3. GetA viene chiamato
  4. Test viene chiamato

Si noti che, nel caso della matrice params, la matrice viene creata subito prima che venga richiamato il metodo di destinazione, dopo che tutti gli argomenti vengono valutati nell'ordine lessicale.

Assegnazione composta

Viene creata e popolata un'istanza della raccolta dopo la valutazione lessicale dell'indice precedente, ma prima che venga valutato l'indice lessicalmente seguente. L'istanza viene usata per invocare il getter e il setter dell'indicizzatore di destinazione.

Per esempio:

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

L'ordine di valutazione è il seguente:

  1. GetA viene chiamato e memorizzato nella cache
  2. MyCollection viene creato, popolato e memorizzato nella cache, GetC viene chiamato nel processo
  3. Il getter dell'indicizzatore viene richiamato con valori memorizzati nella cache per gli indici
  4. Il risultato viene incrementato
  5. Il setter dell'indicizzatore viene richiamato con valori memorizzati nella cache per gli indici e il risultato dell'incremento

Esempio con una raccolta vuota:

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

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

    static int GetA() => 0;
}

L'ordine di valutazione è il seguente:

  1. GetA viene chiamato e memorizzato nella cache
  2. Un MyCollection vuoto viene creato e memorizzato nella cache.
  3. Il getter dell'indicizzatore viene richiamato con valori memorizzati nella cache per gli indici
  4. Il risultato viene incrementato
  5. Il setter dell'indicizzatore viene richiamato con valori memorizzati nella cache per gli indici e il risultato dell'incremento

Inizializzatore di oggetti

Viene creata e popolata un'istanza della raccolta dopo la valutazione lessicale dell'indice precedente, ma prima che venga valutato l'indice lessicalmente seguente. L'istanza viene usata per richiamare il getter dell'indicizzatore quante volte necessario.

Per esempio:

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

L'ordine di valutazione è il seguente:

  1. GetA viene chiamato e memorizzato nella cache
  2. MyCollection viene creato, popolato e memorizzato nella cache, GetC viene chiamato nel processo
  3. Il getter dell'indicizzatore viene richiamato con valori memorizzati nella cache per gli indici
  4. GetF1 viene valutato e assegnato al campo F1 di C1 restituito nel passo precedente
  5. Il getter dell'indicizzatore viene richiamato con valori memorizzati nella cache per gli indici
  6. GetF2 viene valutato e assegnato al campo F2 di C1 restituito nel passo precedente

Si noti che, nel caso della matrice params, i relativi elementi vengono valutati e memorizzati nella cache, ma viene usata una nuova istanza di una matrice (con gli stessi valori all'interno) per ogni chiamata del getter dell'indicizzatore. Per l'esempio precedente, l'ordine di valutazione è il seguente:

  1. GetA viene chiamato e memorizzato nella cache
  2. GetC viene chiamato e memorizzato nella cache
  3. Il getter dell'indicizzatore viene richiamato con GetA nella cache e un nuovo array popolato con GetC nella cache.
  4. GetF1 viene valutato e assegnato al campo F1 di C1 restituito nel passo precedente
  5. Il getter dell'indicizzatore viene richiamato con GetA nella cache e un nuovo array popolato con GetC nella cache.
  6. GetF2 viene valutato e assegnato al campo F2 di C1 restituito nel passo precedente

Esempio con una raccolta vuota:

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

L'ordine di valutazione è il seguente:

  1. GetA viene chiamato e memorizzato nella cache
  2. Un MyCollection vuoto viene creato e memorizzato nella cache.
  3. Il getter dell'indicizzatore viene richiamato con valori memorizzati nella cache per gli indici
  4. GetF1 viene valutato e assegnato al campo F1 di C1 restituito nel passo precedente
  5. Il getter dell'indicizzatore viene richiamato con valori memorizzati nella cache per gli indici
  6. GetF2 viene valutato e assegnato al campo F2 di C1 restituito nel passo precedente

Sicurezza dell'arbitro

La sezione sulla sicurezza delle espressioni di riferimento delle raccolte è applicabile alla costruzione di insiemi di parametri quando le API vengono richiamate nella loro forma espansa.

I parametri params sono scoped in modo implicito quando il tipo è un ref struct. UnscopedRefAttribute può essere usato per eseguirne l'override.

Metadati

Nei metadati, possiamo contrassegnare i parametri params non di matrice con System.ParamArrayAttribute, come oggi vengono contrassegnate le matrici params. Tuttavia, sembra che sarà molto più sicuro usare un attributo diverso per i parametri non di matrice params. Ad esempio, il compilatore VB corrente non sarà in grado di utilizzarli decorati con ParamArrayAttribute né in formato normale, né in formato espanso. Pertanto, è probabile che un'aggiunta del modificatore "params" (parametri) interrompa i consumatori VB e sia molto probabile che interrompa i consumatori provenienti da altri linguaggi o strumenti.

Dato che i parametri di params non di matrice sono contrassegnati con un nuovo System.Runtime.CompilerServices.ParamCollectionAttribute.

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

Questa sezione è stata esaminata in LDM ed è stata approvata.

Domande aperte

Allocazioni dello stack

Di seguito è riportata una citazione da https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#unresolved-questions: "Le allocazioni di stack per collezioni enormi potrebbero causare un overflow dello stack. Il compilatore deve avere un'euristica per inserire questi dati nell'heap? Il linguaggio non deve essere specificato per consentire questa flessibilità? Dobbiamo seguire la specifica per params Span<T>. Pare che dobbiamo rispondere alle domande nel contesto di questa proposta.

[Risolto] Parametri scoped in modo implicito

È stato suggerito che, quando params modifica il parametro ref struct, dovrebbe essere considerato dichiarato scoped. Si sostiene che il numero di casi in cui si desidera limitare l'ambito del parametro sia praticamente pari a 100% quando si esaminano i casi BCL. In alcuni casi in cui è necessario, il valore predefinito può essere sovrascritto con [UnscopedRef].

Tuttavia, potrebbe essere indesiderato modificare l'impostazione predefinita semplicemente in base alla presenza del modificatore params. In particolare, in scenari di override/implementazione, il modificatore params non deve necessariamente corrispondere.

Risoluzione:

I parametri 'params' hanno un ambito implicito - https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-11-15.md#params-improvements.

[Risolto] Considerare l'applicazione di scoped o params su tutte le sovrascritture

In precedenza è stato dichiarato che i parametri di params devono essere scoped per impostazione predefinita. Tuttavia, questo introduce un comportamento dispari nell'override, a causa delle regole esistenti per ripristinare 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!;
}

Esiste una differenza di comportamento tra il trasporto del e il trasporto del tra le sostituzioni: viene ereditato in modo implicito e con esso , mentre da solo non è ereditato in modo implicito e deve essere ripetuto a ogni livello.

Proposta: dovremmo imporre che gli override dei parametri di params debbano dichiarare in modo esplicito params o scoped se la definizione originale è un parametro scoped. In altre parole, s2 in Derived deve avere params, scopedo entrambi.

Risoluzione:

È necessario specificare in modo esplicito scoped o params nel caso di override di un parametro params quando sarebbe necessario utilizzare un parametro diverso daparams, https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-21.md#params-and-scoped-across-overrides.

[Risolto] La presenza di membri obbligatori dovrebbe impedire la dichiarazione del parametro params?

Si consideri l'esempio seguente:

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

Risoluzione:

Convalideremo i membri required rispetto al costruttore usato per determinare l'idoneità a essere un parametro params al punto di dichiarazione, https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-21.md#required-members-and-params-parameters.

Alternative

Esiste una proposta alternativa che estende params solo per ReadOnlySpan<T>.

Inoltre, si potrebbe dire che, ora che le espressioni di raccolta sono nel linguaggio, non è necessario estendere il supporto params. Per qualsiasi tipo di raccolta. Per utilizzare un'API di tipo raccolta, uno sviluppatore deve semplicemente aggiungere due caratteri: [ prima dell'elenco espanso di argomenti e ] dopo. Dato che, l'estensione del supporto params potrebbe essere eccessiva, in particolare che è improbabile che altri linguaggi supportino l'utilizzo di parametri non di matrice params in qualsiasi momento.