LINQ (Language Integrated Query) è una libreria di metodi per filtrare, trasformare, ordinare, raggruppare e riassumere sequenze. Aggiungi using System.Linq; e ogni array, List<T>, Dictionary e altro IEnumerable<T> riceve metodi come Where, Select e OrderBy, ciascuno dei quali accetta una lambda che dice cosa fare con un elemento.
Una prima query: sintassi a metodi e sintassi di query
LINQ ha due notazioni. La sintassi a metodi concatena chiamate; la sintassi di query si legge come SQL. Il compilatore trasforma la sintassi di query nelle stesse chiamate di metodo, quindi producono risultati identici.
Output:
95, 91, 88
95, 91, 88
La sintassi a metodi è più comune nel codice moderno, e alcuni operatori (Take, Distinct, Count, First, ToList) esistono solo come metodi. La sintassi di query dà il meglio con i join e con le query che introducono variabili intermedie con let. Il resto di questa pagina usa la sintassi a metodi e mostra la sintassi di query dove aggiunge qualcosa.
Where e Select: filtrare e proiettare
Where tiene gli elementi che soddisfano una condizione. Select trasforma ogni elemento in qualcos'altro. Insieme coprono la maggior parte delle query quotidiane.
Output:
Mug, Kettle
MUG $8.50 | LAMP $32.00 | KETTLE $27.00 | RUG $120.00
Lamp: 38.40
Rug: 144.00
1. Mug 2. Lamp 3. Kettle 4. Rug
Where non cambia mai gli elementi, e Select non cambia mai quanti sono. new { p.Name, WithTax = ... } crea un tipo anonimo, una classe che il compilatore scrive per te, le cui proprietà leggi con la normale sintassi del punto. Non ha un nome che tu possa scrivere, quindi una variabile che ne contiene uno va dichiarata con var, ed è adatto a risultati che restano dentro un solo metodo.
OrderBy, ThenBy e ordinamento per più chiavi
Output:
Ana 60
Ana 15
Ben 90
Ben 40
Chloe 25
Ben, Ana, Ben, Chloe, Ana
OrderBy è stabile: gli elementi con chiavi uguali restano nel loro ordine originale. Usa ThenBy o ThenByDescending per una seconda chiave. Un secondo OrderBy riordinerebbe tutto in base alla propria chiave, lasciando alla prima chiave solo il ruolo di spareggio: la priorità opposta. Nella sintassi di query, le chiavi sono separate da virgole: orderby o.Customer, o.Total descending.
A differenza di List<T>.Sort, OrderBy lascia intatta la sorgente e restituisce una nuova sequenza.
First, Single, Last e le loro versioni OrDefault
Questi restituiscono un solo elemento invece di una sequenza. Le versioni semplici lanciano un'eccezione quando non c'è niente da restituire; le versioni OrDefault restituiscono il valore predefinito del tipo.
Output:
Ana
Ben
Bea
True
First: InvalidOperationException
Single: InvalidOperationException
0
L'eccezione di First su una sequenza vuota è InvalidOperationException con il messaggio "Sequence contains no elements" (oppure "Sequence contains no matching element" quando un predicato non ha trovato nulla), uno degli errori C# più cercati. Usa FirstOrDefault quando "nessuno" è un risultato normale e controlla se è null.
L'ultima riga mostra la trappola dei tipi valore: per una sequenza di int, FirstOrDefault restituisce 0 quando non trova nulla, e non si può distinguere da un vero 0. Filtra con Where e controlla prima Any(), oppure su .NET 6 e successivi passa un tuo valore predefinito: scores.FirstOrDefault(s => s > 100, -1).
Single serve per "esattamente uno": lancia un'eccezione se non ce n'è nessuno o se ce n'è più di uno, il che lo rende tanto un'asserzione quanto una query.
Any, All, Count e Contains
Output:
True
True
True
3
True
True
Any() si ferma alla prima corrispondenza, quindi items.Any() è il modo giusto per chiedere "c'è qualcosa?". items.Count() > 0 legge la proprietà Count quando la sorgente è una collezione come una lista o un array, ma su una query o un'altra sequenza lazy percorre prima ogni elemento. All su una sequenza vuota restituisce true, perché non c'è nessun elemento che non soddisfi la condizione.
Sum, Average, Min e Max
Output:
Total: 28.25
Items: 7
Priciest: 7.25
Average price: 4.92
0
Average: InvalidOperationException
Sum di una sequenza vuota è 0, ma Average, Min e Max di una sequenza vuota di numeri lanciano InvalidOperationException: non esiste una risposta sensata. Proteggiti con Any(), oppure usa prima DefaultIfEmpty(): empty.DefaultIfEmpty(0).Average().
Distinct, Take e Skip
Output:
home, shop, cart
Page 1: 1 2 3 4 5 6 7 8 9 10
Page 2: 11 12 13 14 15 16 17 18 19 20
Page 3: 21 22 23
12, 15
Distinct tiene la prima occorrenza di ogni valore, in ordine. Skip(n).Take(m) è lo schema standard per la paginazione, e TakeWhile/SkipWhile si fermano o partono dal primo elemento che non soddisfa una condizione. Enumerable.Range(start, count) genera una sequenza di interi senza un array.
GroupBy
GroupBy smista gli elementi in gruppi in base a una chiave. Ogni gruppo ha una Key ed è a sua volta una sequenza, quindi puoi calcolarci sopra degli aggregati.
Output:
North: 3 sales, total 600
South: 2 sales, total 400
East: 1 sales, total 350
Top rep: Ana 400
North -> Ana/Chloe/Ana; South -> Ben/Eli; East -> Dev
I gruppi escono nell'ordine in cui le loro chiavi compaiono per la prima volta nella sorgente (North, South, East), non ordinati; aggiungi OrderBy(g => g.Key) se ti serve. Nella sintassi di query, group X by K sceglie cosa finisce in ogni gruppo (qui solo il nome del venditore) e into g dà un nome al gruppo così la query può continuare.
ToList, ToArray e ToDictionary
I metodi To... eseguono la query e memorizzano i risultati.
Output:
ben@x.com
2
ToDictionary: ArgumentException
ToDictionary lancia ArgumentException quando due elementi producono la stessa chiave, proprio come Dictionary.Add. Se prevedi dei duplicati, raggruppa prima (come fa wordCounts) oppure usa ToLookup, che associa a ogni chiave una sequenza di valori.
Esecuzione differita
Where, Select, OrderBy, GroupBy e la maggior parte degli altri operatori restituiscono un oggetto query e non fanno nulla finché qualcosa non lo enumera. Questo ha due conseguenze che vale la pena vedere:
Output:
25, 40, 99
converting 10
converting 25
converting 40
converting 99
Count: 4
converting 10
converting 25
converting 40
converting 99
Max: 9900
converting 10
converting 25
converting 40
converting 99
Cached: 4, 9900
La prima query ha visto 99 anche se è stato aggiunto dopo che la query era stata scritta: il filtro è stato eseguito quando string.Join l'ha enumerata. La seconda query ha stampato le sue conversioni due volte perché Count() e Max() l'hanno enumerata ciascuno da capo. Con una lambda economica è solo lavoro sprecato; con una query al database, la lettura di un file o un numero casuale dentro la lambda, i due passaggi possono restituire dati diversi. Quando userai un risultato più di una volta, materializzalo con ToList() o ToArray().
Gli operatori che restituiscono un singolo valore (Count, Sum, First, Any, Max) e i metodi To... vengono eseguiti subito. Vedi IEnumerable e yield per come è costruita questa pigrizia.
Riferimento rapido
| Operatore | Cosa fa | Restituisce |
|---|---|---|
Where(x => cond) | Filtra | Sequenza (differita) |
Select(x => expr) | Trasforma ogni elemento | Sequenza (differita) |
SelectMany(x => seq) | Appiattisce sequenze annidate | Sequenza (differita) |
OrderBy, ThenBy (+Descending) | Ordina, in modo stabile | Sequenza (differita) |
GroupBy(x => key) | Raggruppa per chiave | Sequenza di gruppi (differita) |
Distinct, Take, Skip | Rimuove duplicati, pagina | Sequenza (differita) |
First, Single, Last | Un elemento, eccezione se non c'è | Elemento |
FirstOrDefault ecc. | Un elemento o il valore predefinito | Elemento |
Any, All, Contains | Verifica | bool |
Count, Sum, Min, Max, Average | Aggrega | Numero |
ToList, ToArray, ToDictionary | Esegue e memorizza | Collezione |
Errori comuni
First()su una sequenza che può essere vuota. Lancia "Sequence contains no elements"; usaFirstOrDefaulte controlla.- Due chiamate a
OrderBy. La seconda chiave diventa quella principale; usaThenBy. - Enumerare una query più volte. Il lavoro si ripete; chiama
ToList()una volta. - Aspettarsi che una query fotografi la sua sorgente. Legge la sorgente quando viene enumerata, non quando viene scritta.
Average,Min,Maxsu una sequenza vuota. Lanciano un'eccezione; proteggiti conAny()oDefaultIfEmpty.Count() > 0per verificare se ci sono elementi.Any()si ferma al primo.
Domande frequenti
Che cos'è LINQ in C#?
LINQ (Language Integrated Query) è un insieme di metodi di estensione in System.Linq, come Where, Select, OrderBy e GroupBy, che funzionano su qualsiasi IEnumerable<T>: array, liste, dizionari e risultati di altre query. C# ha anche una sintassi di query (from x in items where ... select ...) che il compilatore traduce nelle stesse chiamate di metodo.
Che differenza c'è tra Select e Where in LINQ?
Where filtra: tiene gli elementi per cui la lambda restituisce true e li lascia invariati. Select proietta: trasforma ogni elemento in qualcos'altro, come una proprietà o un nuovo oggetto, e mantiene invariato il numero di elementi. Spesso si concatenano: people.Where(p => p.Age >= 18).Select(p => p.Name).
Che differenza c'è tra First e FirstOrDefault?
First() restituisce il primo elemento (o la prima corrispondenza per un predicato) e lancia InvalidOperationException ("Sequence contains no elements" o "no matching element") quando non ce n'è nessuno. FirstOrDefault() restituisce invece il valore predefinito del tipo: null per le classi e 0 per int, quindi controlla il risultato prima di usarlo.
Come ordino per due campi con LINQ?
Concatena ThenBy dopo OrderBy: orders.OrderBy(o => o.Customer).ThenByDescending(o => o.Total). Chiamare OrderBy due volte non funziona: la seconda chiamata riordina l'intera sequenza, quindi la sua chiave diventa quella principale e la prima chiave serve solo a risolvere i pareggi, il contrario di quello che si voleva.
Come funziona GroupBy in LINQ?
GroupBy(x => key) restituisce un gruppo per ogni chiave distinta. Ogni gruppo è un IGrouping<TKey, TElement>: ha una proprietà Key ed è a sua volta una sequenza degli elementi con quella chiave, quindi puoi chiamarci sopra Count(), Sum() o Select. I gruppi escono nell'ordine in cui compaiono per la prima volta le loro chiavi.
Cos'è l'esecuzione differita in LINQ?
La maggior parte degli operatori LINQ non viene eseguita quando la chiami; restituisce una query che viene eseguita ogni volta che viene enumerata (da foreach, ToList(), Count() e così via). Una query vede quindi le modifiche fatte alla sorgente dopo essere stata definita, ed enumerarla due volte fa il lavoro due volte. Chiama ToList() o ToArray() per eseguirla una volta e conservare i risultati.