Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
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
- Tipo con un metodo di creazione appropriato che può essere richiamato senza argomenti aggiuntivi, che è almeno accessibile come membro dichiarante e con un tipo di elemento corrispondente risultante da tale determinazione
-
struct o tipo di classe che implementa
System.Collections.IEnumerabledove: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)
Adddove:- 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
paramscon i modificatoriin,outoref. 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
Acorrisponda al numero totale di parametri. SeAha 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,outoref, 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,Rxnon è meno specifico diSxe, 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:
-
GetBviene chiamato -
MyCollectionviene creato e popolato,GetCviene chiamato durante il processo -
GetAviene chiamato -
Testviene 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:
-
GetAviene chiamato e memorizzato nella cache -
MyCollectionviene creato, popolato e memorizzato nella cache,GetCviene 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:
-
GetAviene chiamato e memorizzato nella cache - Un
MyCollectionvuoto 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:
-
GetAviene chiamato e memorizzato nella cache -
MyCollectionviene creato, popolato e memorizzato nella cache,GetCviene chiamato nel processo - Il getter dell'indicizzatore viene richiamato con valori memorizzati nella cache per gli indici
-
GetF1viene valutato e assegnato al campoF1diC1restituito nel passo precedente - Il getter dell'indicizzatore viene richiamato con valori memorizzati nella cache per gli indici
-
GetF2viene valutato e assegnato al campoF2diC1restituito 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:
-
GetAviene chiamato e memorizzato nella cache -
GetCviene chiamato e memorizzato nella cache - Il getter dell'indicizzatore viene richiamato con
GetAnella cache e un nuovo array popolato conGetCnella cache. -
GetF1viene valutato e assegnato al campoF1diC1restituito nel passo precedente - Il getter dell'indicizzatore viene richiamato con
GetAnella cache e un nuovo array popolato conGetCnella cache. -
GetF2viene valutato e assegnato al campoF2diC1restituito 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:
-
GetAviene chiamato e memorizzato nella cache - Un
MyCollectionvuoto viene creato e memorizzato nella cache. - Il getter dell'indicizzatore viene richiamato con valori memorizzati nella cache per gli indici
-
GetF1viene valutato e assegnato al campoF1diC1restituito nel passo precedente - Il getter dell'indicizzatore viene richiamato con valori memorizzati nella cache per gli indici
-
GetF2viene valutato e assegnato al campoF2diC1restituito 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 params e il trasporto del scoped negli override: params viene ereditato in modo implicito e con esso scoped, mentre scoped 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.
Proposte correlate
- https://github.com/dotnet/csharplang/issues/1757
- https://github.com/dotnet/csharplang/blob/main/proposals/rejected/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