Relazioni tra i tipi nelle operazioni di query LINQ (C#)

Per scrivere le query in modo efficace, è necessario comprendere in che modo i tipi di variabili in un'operazione di query completa interagiscono tra loro. Se si conoscono queste relazioni, è possibile comprendere più facilmente gli esempi di codice e gli esempi di codice LINQ nella documentazione. Si comprenderà anche cosa accade quando le variabili vengono tipizzate in modo implicito usando var.

Le operazioni di query LINQ sono fortemente tipate nell'origine dati, nella query stessa e nell'esecuzione della query. Il tipo delle variabili nella query deve essere compatibile con il tipo degli elementi nell'origine dati e con il tipo della variabile di iterazione nell'istruzione foreach. Questa forte tipizzazione garantisce che gli errori di tipo vengono rilevati in fase di compilazione quando possono essere corretti prima di essere riscontrati dagli utenti.

Per illustrare tali relazioni del tipo, la maggior parte degli esempi che seguono usa la tipizzazione esplicita per tutte le variabili. L'ultimo esempio mostra come si applicano gli stessi principi anche quando si usa la digitazione implicita usando var.

Query che non trasformano i dati di origine

La figura seguente illustra un'operazione di query LINQ to Objects che non esegue trasformazioni sui dati. L'origine contiene una sequenza di stringhe e anche l'output della query è una sequenza di stringhe.

Diagramma che illustra la relazione dei tipi di dati in una query LINQ.

  1. L'argomento del tipo dell'origine dati determina il tipo della variabile di intervallo.
  2. Il tipo di oggetto selezionato determina il tipo della variabile di query. Qui name è una stringa. La variabile di query è pertanto una IEnumerable<string>.
  3. La variabile di query viene iterata nell'istruzione foreach. Poiché la variabile di query è una sequenza di stringhe, anche la variabile di iterazione è una stringa.

Query che trasformano i dati di origine

La figura seguente illustra un'operazione di query LINQ to SQL che esegue una semplice trasformazione sui dati. La query usa una sequenza di oggetti Customer come input e seleziona solo la proprietà Name nel risultato. Poiché Name è una stringa, la query genera una sequenza di stringhe come output.

Diagramma che illustra una query che trasforma il tipo di dati.

  1. L'argomento del tipo dell'origine dati determina il tipo della variabile di intervallo.
  2. L'istruzione select restituisce la proprietà Name invece dell'oggetto Customer completo. Poiché Name è una stringa, l'argomento del tipo custNameQuery è string e non Customer.
  3. Poiché custNameQuery è una sequenza di stringhe, anche la variabile di iterazione del ciclo foreach del ciclo deve essere una string.

La figura seguente mostra una trasformazione leggermente più complessa. L'istruzione select restituisce un tipo anonimo che acquisisce solo due membri dell'oggetto di origine Customer.

Diagramma che illustra una query più complessa che trasforma il tipo di dati.

  1. L'argomento del tipo dell'origine dati è sempre il tipo della variabile di intervallo nella query.
  2. Poiché l'istruzione select produce un tipo anonimo, la variabile di query deve essere tipizzata in modo implicito tramite var.
  3. Poiché il tipo della variabile di query è implicito, anche la variabile di iterazione nel ciclo foreach deve essere implicita.

Deduzione delle informazioni sul tipo tramite il compilatore

Sebbene sia necessario comprendere le relazioni di tipo in un'operazione di query, è possibile scegliere di far eseguire al compilatore tutto il lavoro al posto dell'utente. La parola chiave var può essere usata per qualsiasi variabile locale in un'operazione di query. La figura seguente è simile all'esempio 2 illustrato in precedenza. Il compilatore fornisce, tuttavia, il tipo forte per ogni variabile nell'operazione di query.

Diagramma che illustra il flusso di tipi con tipizzazione implicita.

Tipi LINQ e generici (C#)

Le query LINQ sono basate su tipi generici. Non è necessaria una conoscenza approfondita dei generics per poter iniziare a scrivere le query. È tuttavia importante comprendere due concetti di base:

  1. Quando si crea un'istanza di una classe di raccolte generiche, ad esempio List<T>, sostituire "T" con il tipo di oggetti che saranno contenuti nell'elenco. Ad esempio, un elenco di stringhe viene espresso come List<string> e un elenco di oggetti Customer viene espresso come List<Customer>. Un elenco generico è fortemente tipizzato e offre molti vantaggi rispetto alle raccolte che archiviano gli elementi come Object. Se si tenta di aggiungere un oggetto Customer a un oggetto List<string>, verrà generato un errore in fase di compilazione. È semplice usare le raccolte generiche poiché non è necessario eseguire il cast dei tipi in fase di esecuzione.
  2. IEnumerable<T> è l'interfaccia che consente alle classi di raccolte generiche di essere enumerate usando l'istruzione foreach. Le classi di raccolte generiche supportano IEnumerable<T> nello stesso modo in cui le classi di raccolte non generiche, come ArrayList, supportano IEnumerable.

Per altre informazioni sui generics, vedere Generics.

Variabili di IEnumerable<T> nelle query LINQ

Le variabili di query LINQ vengono tipizzate come IEnumerable<T> o un tipo derivato, ad esempio IQueryable<T>. Nel caso di una variabile di query tipizzata come IEnumerable<Customer>, significa semplicemente che la query, quando eseguita, genererà una sequenza di zero o più oggetti Customer.

IEnumerable<Customer> customerQuery =
    from cust in customers
    where cust.City == "London"
    select cust;

foreach (Customer customer in customerQuery)
{
    Console.WriteLine($"{customer.LastName}, {customer.FirstName}");
}

Consentire al compilatore di gestire le dichiarazioni di tipo generico

Se si preferisce, è possibile evitare la sintassi generica usando la parola chiave var. La parola chiave var chiede al compilatore di dedurre il tipo di una variabile di query esaminando l'origine dati specificata nella clausola from. Nell'esempio seguente viene generato lo stesso codice compilato dell'esempio precedente:

var customerQuery2 =
    from cust in customers
    where cust.City == "London"
    select cust;

foreach(var customer in customerQuery2)
{
    Console.WriteLine($"{customer.LastName}, {customer.FirstName}");
}

La parola chiave var è utile quando il tipo della variabile è ovvio o quando non è importante specificare in modo esplicito i tipi generici annidati, ad esempio quelli generati dalle query di gruppo. In generale, è consigliabile usare var per rendere più difficile la lettura del codice da parte di altri utenti. Per altre informazioni, vedere Variabili locali tipizzate in modo implicito.