Espressioni di raccolta - Riferimenti al linguaggio C#

È possibile usare un'espressione di raccolta per creare valori di raccolta comuni. Un'espressione di raccolta è una sintassi concisa che, quando valutata, può essere assegnata a molti tipi di raccolta diversi. Un'espressione di raccolta contiene una sequenza di elementi tra parentesi [ e ]. 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);
}

Un'espressione di raccolta può essere convertita 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 l'inizializzazione di una costante o il 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

Si usa 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. La variabile in un elemento spread deve essere enumerabile usando un'istruzione foreach. Come illustrato nell'esempio precedente, è possibile combinare elementi distribuiti con singoli elementi in un'espressione di raccolta.

Conversioni

Un'espressione di raccolta può essere convertita in tipi di raccolta diversi, tra cui:

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 specifica della funzionalità delle espressioni di raccolta 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à:

  • 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 un'espressione di raccolta viene convertita in un Span o ReadOnlySpan, il contesto sicuro dell'oggetto span viene ricavato dal contesto sicuro di tutti gli elementi inclusi nell'intervallo. Per le regole dettagliate, vedere la specifica dell'espressione di raccolta.

Generatore di raccolte

Le espressioni di raccolta funzionano con qualsiasi tipo di raccolta well-behaved. Una raccolta well-behaved presenta le caratteristiche seguenti:

  • Il valore di Count o Length in una raccolta numerabile produce lo stesso valore del numero di elementi quando viene enumerato.
  • Si presuppone che i tipi nello spazio dei nomi System.Collections.Generic siano privi di effetti collaterali. Di conseguenza, il compilatore può ottimizzare gli scenari in cui tali tipi possono essere usati come valori intermedi; in caso contrario non vengono esposti.
  • Una chiamata a un membro .AddRange(x) applicabile in una raccolta comporterà lo stesso valore finale dell'iterazione su x e l'aggiunta di tutti i relativi valori enumerati singolarmente alla raccolta con .Add.

Tutti i tipi di raccolta nel runtime .NET sono well-behaved.

Avviso

Se un tipo di raccolta personalizzato non è well-behaved, il comportamento durante l’utilizzo di tale tipo di raccolta con espressioni di raccolta non è definito.

I tipi acconsentono esplicitamente al supporto dell'espressione di raccolta scrivendo un metodo Create() e applicando il System.Runtime.CompilerServices.CollectionBuilderAttribute sul 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 = new char[80];

    public LineBuffer(ReadOnlySpan<char> buffer)
    {
        int number = (_buffer.Length < buffer.Length) ? _buffer.Length : buffer.Length;
        for (int i = 0; i < number; i++)
        {
            _buffer[i] = buffer[i];
        }
    }

    public IEnumerator<char> GetEnumerator() => _buffer.AsEnumerable<char>().GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => _buffer.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 metodo Create deve restituire un oggetto LineBuffer e deve accettare un singolo parametro 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 avrà il ReadOnlySpan<T> generico come parametro. Il metodo deve essere accessibile e static.

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.