Condividi tramite


Operazioni di proiezione (C#)

La proiezione si riferisce all'operazione di trasformazione di un oggetto in una nuova forma costituita spesso solo dalle proprietà successivamente utilizzate. Utilizzando la proiezione, è possibile costruire un nuovo tipo compilato in base a ogni oggetto. È possibile proiettare una proprietà ed eseguirvi una funzione matematica. È anche possibile proiettare l'oggetto originale senza modificarlo.

Importante

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

Nella sezione seguente sono elencati i metodi dell'operatore query standard che eseguono la proiezione.

Metodi

Nomi dei metodi Descrizione Sintassi dell'espressione di query C# Ulteriori informazioni
Seleziona Proietta i valori che si basano su una funzione di trasformazione. select Enumerable.Select
Queryable.Select
SelectMany Proietta le sequenze di valori che si basano su una funzione di trasformazione semplificandoli in un'unica sequenza. Usare più clausole from Enumerable.SelectMany
Queryable.SelectMany
CAP Produce una sequenza di tuple con elementi da 2-3 sequenze specificate. Non applicabile. Enumerable.Zip
Queryable.Zip

Select

L'esempio seguente usa la clausola select per proiettare la prima lettera di ogni stringa di 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 che usa la sintassi del metodo è mostrata 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

L'esempio seguente usa più clausole from per proiettare tutte le parole di ogni stringa di 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 che usa la sintassi del metodo è mostrata nel codice seguente:

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

var query = phrases.SelectMany(phrases => phrases.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 la combinazione di corrispondenza di ogni elemento nella prima sequenza con ogni 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 che usa la sintassi del metodo è mostrata nel codice seguente:

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

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

Zip

Esistono diversi overload per l'operatore di proiezione Zip. Tutti i metodi Zip funzionano su sequenze di due o più tipi possibilmente eterogenei. I primi due overload restituiscono tuple, con il tipo posizionale corrispondente dalle sequenze indicate.

Si considerino le seguenti raccolte:

// 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 in lunghezza e la sequenza risultante omette l'ultimo elemento della raccolta numbers poiché non ha nessun elemento per la compressione.

Il secondo overload accetta una sequenza third. È possibile creare 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 metodo Zip proietta una tupla, ma questa volta con tre elementi.

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

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 l'overload Zip precedente, la funzione specificata viene applicata agli elementi corrispondenti numbers e letter, producendo una sequenza dei risultati string.

Select e SelectMany

La funzione di Select e SelectMany è produrre uno o più valori risultato dai valori di origine. Select produce un valore risultato per ogni valore di origine. Il risultato complessivo è pertanto una raccolta contenente lo stesso numero di elementi della raccolta di origine. Per contro, SelectMany produce un unico risultato complessivo che contiene sottoraccolte concatenate da ciascun 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 di grandi dimensioni.

Le due figure seguenti illustrano la differenza concettuale tra le azioni di questi due metodi. In ogni caso, si supponga che la funzione del selettore (trasformazione) selezioni la matrice di fiori di ogni valore di origine.

La figura mostra che Select restituisce una raccolta contenente lo stesso numero di elementi della raccolta di origine.

Elemento grafico che illustra l'azione di Select()

La figura mostra che SelectMany concatena la sequenza intermedia di matrici in un unico valore risultato finale contenente tutti i valori di ogni matrice intermedia.

Elemento grafico che illustra l'azione di SelectMany()

Esempio di codice

L'esempio seguente confronta 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 che segue, il "valore singolo" usato dalla funzione di trasformazione Select<TSource,TResult>(IEnumerable<TSource>, Func<TSource,TResult>) è una raccolta di valori. Questo esempio richiede il ciclo foreach aggiuntivo 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);
    }
}

Vedi anche