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.
Usare un'espressione di raccolta per creare valori di raccolta comuni.
Un'espressione di raccolta è una sintassi terse che è possibile assegnare a molti tipi di raccolta diversi. Un'espressione di raccolta contiene una sequenza di elementi tra parentesi [ e ].
Il riferimento al linguaggio C# documenta la versione rilasciata più di recente del linguaggio C#. Contiene anche la documentazione iniziale per le funzionalità nelle versioni di anteprima pubblica per la prossima versione del linguaggio di programmazione.
La documentazione identifica tutte le funzionalità introdotte nelle ultime tre versioni della lingua o nelle anteprime pubbliche correnti.
Suggerimento
Per trovare quando una funzionalità è stata introdotta per la prima volta in C#, vedere l'articolo sulla cronologia delle versioni del linguaggio C#.
Nell'esempio seguente viene dichiarato un System.Span<T> di elementi string e li inizializza nei giorni della settimana:
Span<string> weekDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
foreach (var day in weekDays)
{
Console.WriteLine(day);
}
È possibile convertire un'espressione di raccolta in molti tipi di raccolta diversi. Il primo esempio ha illustrato come inizializzare una variabile usando un'espressione di raccolta. Il codice seguente mostra molte altre posizioni in cui è possibile usare un'espressione di raccolta:
// Initialize private field:
private static readonly ImmutableArray<string> _months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
// property with expression body:
public IEnumerable<int> MaxDays =>
[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
public int Sum(IEnumerable<int> values) =>
values.Sum();
public void Example()
{
// As a parameter:
int sum = Sum([1, 2, 3, 4, 5]);
}
Non è possibile usare un'espressione di raccolta in cui è prevista una costante in fase di compilazione, ad esempio durante l'inizializzazione di una costante o come valore predefinito per un argomento del metodo.
Entrambi gli esempi precedenti utilizzavano costanti come elementi di un'espressione di raccolta. È anche possibile usare le variabili per gli elementi, come illustrato nell'esempio seguente:
string hydrogen = "H";
string helium = "He";
string lithium = "Li";
string beryllium = "Be";
string boron = "B";
string carbon = "C";
string nitrogen = "N";
string oxygen = "O";
string fluorine = "F";
string neon = "Ne";
string[] elements = [hydrogen, helium, lithium, beryllium, boron, carbon, nitrogen, oxygen, fluorine, neon];
foreach (var element in elements)
{
Console.WriteLine(element);
}
Elemento Spread
Utilizzare un elemento.. spread per i valori della raccolta inline in un'espressione di raccolta. Nell'esempio seguente viene creata una raccolta per l'alfabeto completo combinando una raccolta di vocali, una raccolta di consonanti e la lettera "y", che può essere:
string[] vowels = ["a", "e", "i", "o", "u"];
string[] consonants = ["b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
"n", "p", "q", "r", "s", "t", "v", "w", "x", "z"];
string[] alphabet = [.. vowels, .. consonants, "y"];
L'elemento spread ..vowels, quando valutato, produce cinque elementi: "a", "e", "i", "o" e "u". L'elemento spread ..consonants produce 20 elementi, il numero nella matrice consonants. L'espressione in un elemento spread deve essere enumerabile tramite un'istruzione foreach . Come illustrato nell'esempio precedente, è possibile combinare elementi distribuiti con singoli elementi in un'espressione di raccolta.
Conversioni
È possibile convertire un'espressione di raccolta in tipi di raccolta diversi, tra cui:
- System.Span<T> e System.ReadOnlySpan<T>.
-
Matrici, ad esempio
int[]ostring[]. - Qualsiasi tipo con un metodo crea il cui tipo di parametro è
ReadOnlySpan<T>in cui è presente una conversione implicita dal tipo di espressione di raccolta aT. - Qualsiasi tipo che supporta un inizializzatore di raccolta, ad esempio System.Collections.Generic.List<T>. In genere, questo requisito indica che il tipo supporta System.Collections.Generic.IEnumerable<T> ed è disponibile un metodo
Addaccessibile per aggiungere elementi alla raccolta. Deve essere presente una conversione implicita dal tipo degli elementi dell'espressione raccolta al tipo di elemento della raccolta. Per gli elementi distribuiti, deve essere presente una conversione implicita dal tipo dell'elemento spread al tipo di elemento della raccolta. - Una delle interfacce seguenti:
Annotazioni
Non è possibile usare espressioni di raccolta per inizializzare matrici inline. Le matrici inline richiedono una sintassi di inizializzazione diversa.
Importante
Un'espressione di raccolta crea sempre una raccolta che include tutti gli elementi dell'espressione di raccolta, indipendentemente dal tipo di destinazione della conversione. Ad esempio, quando la destinazione della conversione è System.Collections.Generic.IEnumerable<T>, il codice generato valuta l'espressione di raccolta e archivia i risultati in una raccolta in memoria.
Questo comportamento è diverso da quello di LINQ, in cui potrebbe non essere creata un'istanza di una sequenza fino a quando non viene enumerata. Non è possibile usare espressioni di raccolta per generare una sequenza infinita che non verrà enumerata.
Il compilatore usa l'analisi statica per determinare il modo più efficiente per creare la raccolta dichiarata con un'espressione di raccolta. Ad esempio, l'espressione di raccolta vuota, [], può essere realizzata come Array.Empty<T>() se la destinazione non verrà modificata dopo l'inizializzazione. Quando la destinazione è System.Span<T> o System.ReadOnlySpan<T>, l'archiviazione potrebbe essere allocata nello stack. La clausola delle espressioni di raccolta §12.8.25 dello standard C# specifica le regole che il compilatore deve seguire.
Molte API sono sottoposte a overload con più tipi di raccolta come parametri. Poiché un'espressione di raccolta può essere convertita in molti tipi di espressione diversi, queste API potrebbero richiedere cast sull'espressione di raccolta per specificare la conversione corretta. Le regole di conversione seguenti risolvono alcune delle ambiguità:
- Una conversione migliore degli elementi è preferibile rispetto a una migliore conversione dei tipi di raccolta. In altre parole, il tipo di elementi nell'espressione di raccolta ha un'importanza maggiore rispetto al tipo della raccolta. Queste regole sono descritte nella conversione migliore dalla clausola expression della specifica del linguaggio C#.
- La conversione in Span<T>, ReadOnlySpan<T> o un altro tipo
ref structè preferibile rispetto a una conversione in un tipo di struct non ref. - La conversione in un tipo non di interfaccia è migliore di una conversione in un tipo di interfaccia.
Quando si converte un'espressione di raccolta in un Span oggetto o ReadOnlySpan, il contesto sicuro dell'oggetto span proviene dal contesto sicuro di tutti gli elementi inclusi nell'intervallo. Per le regole dettagliate, vedere Contesti sicuri di riferimento di paragrafo 9.7.2 nello standard C#.
Generatore di raccolte
Le espressioni di raccolta funzionano con qualsiasi tipo di raccolta ben comportato. Una raccolta well-behaved presenta le caratteristiche seguenti:
- Il valore di
CountoLengthin una raccolta conteggiabile produce lo stesso valore del numero di elementi quando si enumera la raccolta. - I tipi nello System.Collections.Generic spazio dei nomi sono privi di effetti collaterali. Il compilatore ottimizza gli scenari in cui questi tipi possono essere usati come valori intermedi, ma non li espone in caso contrario.
- Una chiamata a un membro applicabile
.AddRange(x)in una raccolta restituisce lo stesso valore finale dell'iterazionexe l'aggiunta di tutti i relativi valori enumerati singolarmente alla raccolta utilizzando.Add.
Tutti i tipi di raccolta nel runtime .NET sono well-behaved.
Avviso
Se un tipo di raccolta personalizzato non è corretto, il comportamento non è definito quando si usa tale tipo di raccolta con espressioni di raccolta.
I tipi optano per il supporto dell'espressione di raccolta scrivendo un Create() metodo e applicando l'attributo System.Runtime.CompilerServices.CollectionBuilderAttribute al tipo di raccolta per indicare il metodo builder. Si consideri, ad esempio, un'applicazione che usa buffer a lunghezza fissa di 80 caratteri. La classe potrebbe avere un aspetto simile al codice seguente:
public class LineBuffer : IEnumerable<char>
{
private readonly char[] _buffer;
private readonly int _count;
public LineBuffer(ReadOnlySpan<char> buffer)
{
_buffer = new char[buffer.Length];
_count = buffer.Length;
for (int i = 0; i < _count; i++)
{
_buffer[i] = buffer[i];
}
}
public int Count => _count;
public char this[int index]
{
get
{
if (index >= _count)
throw new IndexOutOfRangeException();
return _buffer[index];
}
}
public IEnumerator<char> GetEnumerator()
{
for (int i = 0; i < _count; i++)
{
yield return _buffer[i];
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
// etc
}
Si vuole usarlo con le espressioni di raccolta, come illustrato nell'esempio seguente:
LineBuffer line = ['H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '!'];
Il tipo LineBuffer implementa IEnumerable<char>, quindi il compilatore lo riconosce come una raccolta di elementi char. Il parametro di tipo dell'interfaccia System.Collections.Generic.IEnumerable<T> implementata indica il tipo di elemento. È necessario apportare due aggiunte all'applicazione per poter assegnare espressioni di raccolta a un oggetto LineBuffer. Prima di tutto, è necessario creare una classe contenente un metodo Create:
internal static class LineBufferBuilder
{
internal static LineBuffer Create(ReadOnlySpan<char> values) => new LineBuffer(values);
}
Il Create metodo deve restituire un LineBuffer oggetto e deve accettare un parametro finale del tipo ReadOnlySpan<char>. Il parametro di tipo di ReadOnlySpan deve corrispondere al tipo di elemento della raccolta. Un metodo builder che restituisce una raccolta generica ha il generico ReadOnlySpan<T> come parametro. Il metodo deve essere accessibile e static.
A partire da C# 15, il Create metodo può avere parametri aggiuntivi prima del ReadOnlySpan<T> parametro . È possibile passare valori a questi parametri usando un with(...) elemento nell'espressione di raccolta. Per informazioni dettagliate, vedere Argomenti del generatore di raccolte.
Infine, è necessario aggiungere CollectionBuilderAttribute alla dichiarazione di classe LineBuffer:
[CollectionBuilder(typeof(LineBufferBuilder), "Create")]
Il primo parametro fornisce il nome della classe Builder. Il secondo attributo fornisce il nome del metodo builder.
Argomenti dell'espressione di raccolta
A partire da C# 15, è possibile passare argomenti al costruttore o al metodo factory della raccolta sottostante usando un with(...) elemento come primo elemento in un'espressione di raccolta. Questa funzionalità consente di specificare capacità, comparer o altri parametri del costruttore direttamente all'interno della sintassi dell'espressione di raccolta. Per altre informazioni, vedere la specifica della funzionalità degli argomenti delle espressioni di raccolta.
L'elemento with(...) deve essere il primo elemento nell'espressione di raccolta. Gli argomenti dichiarati nell'elemento with(...) passano al costruttore appropriato o creano il metodo in base al tipo di destinazione. È possibile usare qualsiasi espressione valida per gli argomenti nell'elemento with .
Le espressioni di argomento e le espressioni di elemento vengono valutate una sola volta, in ordine da sinistra a destra, con gli with(...) argomenti valutati prima degli elementi della raccolta. Per una distribuzione come .. GetItems(), le chiamate GetItems() di valutazione per ottenere la raccolta da distribuire. L'enumerazione è il passaggio separato che legge gli elementi dalla raccolta e li aggiunge al risultato. Il compilatore valuta GetItems() la posizione da sinistra a destra, ma può ritardare l'enumerazione fino a quando non valuta le espressioni di elemento successive. Non fare affidamento sugli effetti collaterali dell'enumerazione che si verificano prima di quelle espressioni successive.
Argomenti del costruttore
Quando il tipo di destinazione è una classe o uno struct che implementa System.Collections.IEnumerable, gli argomenti in with(...) vengono valutati e i risultati passano al costruttore. Il compilatore usa la risoluzione dell'overload per selezionare il costruttore corrispondente migliore:
public void CollectionArgumentsExamples()
{
string[] values = ["one", "two", "three"];
// Pass capacity argument to List<T> constructor
List<string> names = [with(capacity: values.Length * 2), .. values];
// Pass comparer argument to HashSet<T> constructor
HashSet<string> set = [with(StringComparer.OrdinalIgnoreCase), "Hello", "HELLO", "hello"];
// set contains only one element because all strings are equal with OrdinalIgnoreCase
// Pass capacity to IList<T> (uses List<T> constructor)
IList<int> numbers = [with(capacity: 100), 1, 2, 3];
}
Nell'esempio precedente:
- Il
List<string>costruttore con uncapacityparametro viene chiamato convalues.Length * 2. - Il
HashSet<string>costruttore con un System.Collections.Generic.IEqualityComparer<T> parametro viene chiamato conStringComparer.OrdinalIgnoreCase. - Per i tipi di destinazione dell'interfaccia come System.Collections.Generic.IList<T>, il compilatore crea un oggetto
List<T>con la capacità specificata.
Per un tipo di destinazione costruito in questo modo, with(...) è necessario se il tipo non dispone di un costruttore senza parametri accessibile. Usarlo per fornire gli argomenti a un costruttore accessibile. Ad esempio, NamedValues richiede un nome e eredita il comportamento della raccolta di List<int>:
public class NamedValues(string name) : List<int>
{
public string Name { get; } = name;
}
Questa espressione di raccolta fornisce il nome richiesto e quindi aggiunge gli elementi:
NamedValues readings = [with("temperature"), 18, 21, 24];
Console.WriteLine($"{readings.Name}: {string.Join(", ", readings)}");
// output: temperature: 18, 21, 24
Argomenti del generatore di raccolte
Per i tipi con un System.Runtime.CompilerServices.CollectionBuilderAttributeoggetto , l'elemento with(...) valuta gli argomenti dichiarati e invia i risultati al metodo create prima del ReadOnlySpan<T> parametro . Questa funzionalità consente di creare metodi per accettare parametri di configurazione:
internal static class MySetBuilder
{
internal static MySet<T> Create<T>(ReadOnlySpan<T> items) => new MySet<T>(items);
internal static MySet<T> Create<T>(IEqualityComparer<T> comparer, ReadOnlySpan<T> items) =>
new MySet<T>(items, comparer);
}
Usare l'elemento per passare l'operatore with(...) di confronto:
public void CollectionBuilderArgumentsExample()
{
// Pass comparer to a type with CollectionBuilder attribute
// The comparer argument is passed before the ReadOnlySpan<T> parameter
MySet<string> mySet = [with(StringComparer.OrdinalIgnoreCase), "A", "a", "B"];
// mySet contains only two elements: "A" and "B"
}
La risoluzione dell'overload seleziona il metodo di creazione in base agli argomenti specificati. L'oggetto ReadOnlySpan<T> contenente gli elementi della raccolta è sempre l'ultimo parametro.
Tipi di destinazione dell'interfaccia
Diversi tipi di destinazione dell'interfaccia supportano gli argomenti delle espressioni di raccolta. La tabella seguente illustra le interfacce supportate e le relative firme del costruttore applicabili:
| Interfaccia | Elementi supportati with |
|---|---|
| IEnumerable<T>, IReadOnlyCollection<T>, IReadOnlyList<T> |
() (solo vuoto) |
| ICollection<T>, IList<T> |
(), (int capacity) |
Per IList<T> e ICollection<T>, il compilatore usa un System.Collections.Generic.List<T> oggetto con il costruttore specificato.
Restrictions
L'elemento with(...) presenta le restrizioni seguenti:
- Deve essere il primo elemento nell'espressione della raccolta.
- Gli argomenti non possono avere
dynamicun tipo. - Non è supportato per matrici o tipi span (
Span<T>,ReadOnlySpan<T>). - Gli argomenti in
with(...)non influiscono sull'esistenza di una conversione dall'espressione di raccolta a un tipo di destinazione candidato. La risoluzione dell'overload e l'inferenza del tipo per una chiamata di inclusione ignorano tali argomenti. Solo la presenza di unwith(...)elemento, non dei relativi argomenti, influisce sul fatto che tale conversione esista. Dopo aver determinato il tipo di destinazione, gli argomenti partecipano alla risoluzione dell'overload per il relativo metodo costruttore o generatore di raccolte.
Ad esempio, un argomento dell'operatore di confronto non indica al compilatore di scegliere un HashSet<string> overload su un List<string> overload:
class Examples
{
static void Print(List<string> values) { }
static void Print(HashSet<string> values) { }
static void Example()
{
Print([with(StringComparer.OrdinalIgnoreCase), "one", "ONE"]);
}
}
La chiamata produce CS0121: "La chiamata è ambigua tra i metodi o le proprietà seguenti". Anche se dispone solo HashSet<string> di un costruttore che accetta questo operatore di confronto, il compilatore ignora gli with(...) argomenti quando sceglie l'overload Print . Assegnare prima l'espressione di raccolta a una HashSet<string> variabile per selezionare l'overload.
Analogamente, with(...) gli argomenti non forniscono tipi di elemento per l'inferenza del tipo generico:
class Examples
{
static void Use<T>(List<T> values) { }
static void Example()
{
Use([with(10)]);
}
}
Questa chiamata produce CS0411: "Gli argomenti di tipo non possono essere dedotti dall'utilizzo. Provare a specificare gli argomenti di tipo in modo esplicito." L'argomento capacity non determina Te la raccolta non dispone di elementi da cui dedurrlo. Specificare l'argomento di tipo, ad esempio Use<int>([with(10)]).