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 IEnumerable
normale. 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 modificatoriin
,out
oref
. 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. SeA
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
,out
oref
, 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
- la modalità di passaggio dei parametri dell'argomento è identica a quella del parametro corrispondente e
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ᵥ
aQᵥ
non è migliore della conversione implicita daEᵥ
aPᵥ
e - per almeno un argomento, la conversione da
Eᵥ
aPᵥ
è migliore rispetto alla conversione daEᵥ
aQᵥ
.
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 eMₑ
è un metodo generico,Mᵢ
è migliore diMₑ
. - In caso contrario, se
Mᵢ
è applicabile nella forma normale eMₑ
ha una raccolta di parametri ed è applicabile solo nel formato espanso,Mᵢ
è migliore diMₑ
. - 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 diMₑ
,Mᵢ
è migliore diMₑ
. - In caso contrario, se
Mᵥ
ha tipi di parametri più specifici diMₓ
,Mᵥ
è migliore diMₓ
. Lasciare che{R1, R2, ..., Rn}
e{S1, S2, ..., Sn}
rappresentino i tipi di parametro non istanziati e non espansi diMᵥ
eMₓ
. i tipi di parametro diMᵥ
sono più specifici diMₓ
se, per ogni parametro,Rx
non è meno specifico diSx
e, per almeno un parametro,Rx
è più specifico diSx
:- 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 inMₓ
,Mᵥ
è migliore diMₓ
. - Se per almeno un parametro
Mᵥ
usa la scelta migliore per il passaggio di parametri (§12.6.4.4) rispetto al parametro corrispondente inMₓ
e nessuno dei parametri inMₓ
usa una scelta di passaggio dei parametri migliore diMᵥ
,Mᵥ
è migliore diMₓ
. -
In caso contrario, se entrambi i metodi dispongono di raccolte di parametri e sono applicabili solo nelle loro forme espanse,
Mᵢ
è preferibile aMₑ
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 diMₑ
-
la raccolta di parametri di
Mᵢ
èSystem.ReadOnlySpan<Eᵢ>
, e la raccolta di parametri diMₑ
èSystem.Span<Eₑ>
, ed esiste una conversione di identità daEᵢ
aEₑ
-
raccolta di parametri di
Mᵢ
èSystem.ReadOnlySpan<Eᵢ>
oSystem.Span<Eᵢ>
e la raccolta di parametri diMₑ
è un array_or_array_interface__type con tipo di elementoEₑ
ed esiste una conversione di identità daEᵢ
aEₑ
-
entrambe le raccolte params non sono span_type, ed esiste una conversione implicita dalla raccolta params di
- 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 diMₑ
è 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:
-
GetB
viene chiamato -
MyCollection
viene creato e popolato,GetC
viene chiamato durante il processo -
GetA
viene chiamato -
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:
-
GetA
viene chiamato e memorizzato nella cache -
MyCollection
viene creato, popolato e memorizzato nella cache,GetC
viene chiamato nel processo - Il getter dell'indicizzatore viene richiamato con valori memorizzati nella cache per gli indici
- Il risultato viene incrementato
- 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:
-
GetA
viene chiamato e memorizzato nella cache - Un
MyCollection
vuoto viene creato e memorizzato nella cache. - Il getter dell'indicizzatore viene richiamato con valori memorizzati nella cache per gli indici
- Il risultato viene incrementato
- 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:
-
GetA
viene chiamato e memorizzato nella cache -
MyCollection
viene creato, popolato e memorizzato nella cache,GetC
viene chiamato nel processo - Il getter dell'indicizzatore viene richiamato con valori memorizzati nella cache per gli indici
-
GetF1
viene valutato e assegnato al campoF1
diC1
restituito nel passo precedente - Il getter dell'indicizzatore viene richiamato con valori memorizzati nella cache per gli indici
-
GetF2
viene valutato e assegnato al campoF2
diC1
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:
-
GetA
viene chiamato e memorizzato nella cache -
GetC
viene chiamato e memorizzato nella cache - Il getter dell'indicizzatore viene richiamato con
GetA
nella cache e un nuovo array popolato conGetC
nella cache. -
GetF1
viene valutato e assegnato al campoF1
diC1
restituito nel passo precedente - Il getter dell'indicizzatore viene richiamato con
GetA
nella cache e un nuovo array popolato conGetC
nella cache. -
GetF2
viene valutato e assegnato al campoF2
diC1
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:
-
GetA
viene chiamato e memorizzato nella cache - Un
MyCollection
vuoto viene creato e memorizzato nella cache. - Il getter dell'indicizzatore viene richiamato con valori memorizzati nella cache per gli indici
-
GetF1
viene valutato e assegnato al campoF1
diC1
restituito nel passo precedente - Il getter dell'indicizzatore viene richiamato con valori memorizzati nella cache per gli indici
-
GetF2
viene valutato e assegnato al campoF2
diC1
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
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
, scoped
o 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.
Proposte correlate
- https://github.com/dotnet/csharplang/issues/1757
- https://github.com/dotnet/csharplang/blob/main/proposals/format.md#extending-params
Riunioni di progettazione correlate
- 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