Condividi tramite


Operazioni di proiezione (C#)

La proiezione fa riferimento all'operazione di trasformazione di un oggetto in un nuovo formato che spesso è costituito solo da tali proprietà usate successivamente. Usando la proiezione, è possibile costruire un nuovo tipo compilato da ogni oggetto. È possibile proiettare una proprietà ed eseguire una funzione matematica su di essa. È anche possibile proiettare l'oggetto originale senza modificarlo.

Importante

In questi esempi viene usata un'origine dati System.Collections.Generic.IEnumerable<T>. Le sorgenti di dati basate su System.Linq.IQueryProvider utilizzano sorgenti di dati System.Linq.IQueryable<T> e alberi di espressioni. La sintassi consentita in C# per gli alberi delle espressioni ha limitazioni. Inoltre, ogni origine dati IQueryProvider, ad esempio EF Core può imporre altre restrizioni. Consultare la documentazione relativa all'origine dati.

I metodi dell'operatore di query standard che eseguono la proiezione sono elencati nella sezione seguente.

Metodi

Nomi dei metodi Descrizione Sintassi delle espressioni di query C# Maggiori informazioni
Seleziona Proietta valori basati su una funzione di trasformazione. select Enumerable.Select
Queryable.Select
SelectMany Proietta sequenze di valori basati su una funzione di trasformazione e quindi li appiattisce in un'unica sequenza. Usare più from clausole Enumerable.SelectMany
Queryable.SelectMany
Compressione ZIP Producono una sequenza di tuple con elementi provenienti da 2 o 3 sequenze specificate. Non applicabile. Enumerable.Zip
Queryable.Zip

Select

Nell'esempio seguente viene utilizzata la select clausola per proiettare la prima lettera di ogni stringa in un elenco di stringhe.

List<string> words = ["an", "apple", "a", "day"];

var query = from word in words
            select word.Substring(0, 1);

foreach (string s in query)
{
    Console.WriteLine(s);
}

/* This code produces the following output:

    a
    a
    a
    d
*/

La query equivalente tramite la sintassi del metodo è illustrata nel codice seguente:

List<string> words = ["an", "apple", "a", "day"];

var query = words.Select(word => word.Substring(0, 1));

foreach (string s in query)
{
    Console.WriteLine(s);
}

/* This code produces the following output:

    a
    a
    a
    d
*/

SelectMany

Nell'esempio seguente vengono usate più from clausole per proiettare ogni parola di ogni stringa in un elenco di stringhe.

List<string> phrases = ["an apple a day", "the quick brown fox"];

var query = from phrase in phrases
            from word in phrase.Split(' ')
            select word;

foreach (string s in query)
{
    Console.WriteLine(s);
}

/* This code produces the following output:

    an
    apple
    a
    day
    the
    quick
    brown
    fox
*/

La query equivalente tramite la sintassi del metodo è illustrata nel codice seguente:

List<string> phrases = ["an apple a day", "the quick brown fox"];

var query = phrases.SelectMany(phrase => phrase.Split(' '));

foreach (string s in query)
{
    Console.WriteLine(s);
}

/* This code produces the following output:

    an
    apple
    a
    day
    the
    quick
    brown
    fox
*/

Il metodo SelectMany può anche formare una combinazione che corrisponde ciascun elemento nella prima sequenza a ciascun elemento nella seconda sequenza.

var query = from number in numbers
            from letter in letters
            select (number, letter);

foreach (var item in query)
{
    Console.WriteLine(item);
}

La query equivalente tramite la sintassi del metodo è illustrata nel codice seguente:

var method = numbers
    .SelectMany(number => letters,
    (number, letter) => (number, letter));

foreach (var item in method)
{
    Console.WriteLine(item);
}

Zip

Esistono diversi sovraccarichi per l'operatore di proiezione Zip. Tutti i Zip metodi funzionano su sequenze di due o più tipi eterogeni. I primi due overload restituiscono delle tuple, con il tipo posizionale corrispondente alle sequenze fornite.

Si considerino le raccolte seguenti:

// An int array with 7 elements.
IEnumerable<int> numbers = [1, 2, 3, 4, 5, 6, 7];
// A char array with 6 elements.
IEnumerable<char> letters = ['A', 'B', 'C', 'D', 'E', 'F'];

Per proiettare queste sequenze insieme, usare l'operatore Enumerable.Zip<TFirst,TSecond>(IEnumerable<TFirst>, IEnumerable<TSecond>) :

foreach ((int number, char letter) in numbers.Zip(letters))
{
    Console.WriteLine($"Number: {number} zipped with letter: '{letter}'");
}
// This code produces the following output:
//     Number: 1 zipped with letter: 'A'
//     Number: 2 zipped with letter: 'B'
//     Number: 3 zipped with letter: 'C'
//     Number: 4 zipped with letter: 'D'
//     Number: 5 zipped with letter: 'E'
//     Number: 6 zipped with letter: 'F'

Importante

La sequenza risultante da un'operazione zip non è mai più lunga della sequenza più breve. Le raccolte numbers e letters differiscono per lunghezza, e la sequenza risultante omette l'ultimo elemento dalla raccolta numbers, poiché non ha nulla con cui accoppiare.

Il secondo overload accetta una third sequenza. Si creerà un'altra raccolta, ovvero emoji:

// A string array with 8 elements.
IEnumerable<string> emoji = [ "🤓", "🔥", "🎉", "👀", "⭐", "💜", "✔", "💯"];

Per proiettare queste sequenze insieme, usare l'operatore Enumerable.Zip<TFirst,TSecond,TThird>(IEnumerable<TFirst>, IEnumerable<TSecond>, IEnumerable<TThird>) :

foreach ((int number, char letter, string em) in numbers.Zip(letters, emoji))
{
    Console.WriteLine(
        $"Number: {number} is zipped with letter: '{letter}' and emoji: {em}");
}
// This code produces the following output:
//     Number: 1 is zipped with letter: 'A' and emoji: 🤓
//     Number: 2 is zipped with letter: 'B' and emoji: 🔥
//     Number: 3 is zipped with letter: 'C' and emoji: 🎉
//     Number: 4 is zipped with letter: 'D' and emoji: 👀
//     Number: 5 is zipped with letter: 'E' and emoji: ⭐
//     Number: 6 is zipped with letter: 'F' and emoji: 💜

Analogamente all'overload precedente, il Zip metodo proietta una tupla, ma questa volta con tre elementi.

Il terzo overload accetta un Func<TFirst, TSecond, TResult> argomento che funge da selettore di risultati. È possibile proiettare una nuova sequenza risultante dalle sequenze combinate.

foreach (string result in
    numbers.Zip(letters, (number, letter) => $"{number} = {letter} ({(int)letter})"))
{
    Console.WriteLine(result);
}
// This code produces the following output:
//     1 = A (65)
//     2 = B (66)
//     3 = C (67)
//     4 = D (68)
//     5 = E (69)
//     6 = F (70)

Con il precedente overload Zip, la funzione specificata viene applicata agli elementi corrispondenti number e letter, producendo una sequenza di risultati string.

Select contro SelectMany

Il lavoro di Select e SelectMany consiste nel produrre uno o più valori di risultato dai valori di origine. Select produce un valore di risultato per ogni valore di origine. Il risultato complessivo è quindi una raccolta con lo stesso numero di elementi della raccolta di origine. Al contrario, SelectMany produce un singolo risultato complessivo che contiene sottocollezioni concatenate da ogni valore di origine. La funzione di trasformazione passata come argomento a SelectMany deve restituire una sequenza enumerabile di valori per ogni valore di origine. SelectMany concatena queste sequenze enumerabili per creare una sequenza grande.

Le due illustrazioni seguenti illustrano la differenza concettuale tra le azioni di questi due metodi. In ogni caso, si supponga che la funzione selettore (transform) selezioni l'array di fiori da ciascun valore sorgente.

Questa figura illustra come Select restituisce una raccolta con lo stesso numero di elementi della raccolta di origine.

Immagine che mostra l'azione di Select()

Questa figura illustra come SelectMany concatenare la sequenza intermedia di matrici in un valore finale del risultato che contiene ogni valore di ogni matrice intermedia.

Immagine che mostra l'azione di SelectMany()

Esempio di codice

Nell'esempio seguente viene confrontato il comportamento di Select e SelectMany. Il codice crea un "bouquet" di fiori prendendo gli elementi da ogni elenco di nomi di fiori nella raccolta di origine. Nell'esempio seguente, il "singolo valore" usato dalla funzione Select<TSource,TResult>(IEnumerable<TSource>, Func<TSource,TResult>) di trasformazione è una raccolta di valori. Questo esempio richiede il ciclo aggiuntivo foreach per enumerare ogni stringa in ogni sottosequenza.

class Bouquet
{
    public required List<string> Flowers { get; init; }
}

static void SelectVsSelectMany()
{
    List<Bouquet> bouquets =
    [
        new Bouquet { Flowers = ["sunflower", "daisy", "daffodil", "larkspur"] },
        new Bouquet { Flowers = ["tulip", "rose", "orchid"] },
        new Bouquet { Flowers = ["gladiolis", "lily", "snapdragon", "aster", "protea"] },
        new Bouquet { Flowers = ["larkspur", "lilac", "iris", "dahlia"] }
    ];

    IEnumerable<List<string>> query1 = bouquets.Select(bq => bq.Flowers);

    IEnumerable<string> query2 = bouquets.SelectMany(bq => bq.Flowers);

    Console.WriteLine("Results by using Select():");
    // Note the extra foreach loop here.
    foreach (IEnumerable<string> collection in query1)
    {
        foreach (string item in collection)
        {
            Console.WriteLine(item);
        }
    }

    Console.WriteLine("\nResults by using SelectMany():");
    foreach (string item in query2)
    {
        Console.WriteLine(item);
    }
}

Vedere anche