LINQ
Orice program care lucrează cu colecții ajunge la aceleași operații: filtrare, sortare, transformare, agregare. Fără un mecanism dedicat, fiecare operație se scrie ca o buclă explicită cu variabile contor, condiții de filtrare și variabile de acumulare. Codul este verbose, ușor de greșit și dificil de citit dintr-o privire.
Considerați calculul sumei prețurilor finale ale tuturor biletelor valide:
// Fara LINQ — verbose si predispus la erori
double total = 0;
foreach (Bilet b in bileteCumparate)
{
if (b.EsteValid())
total += b.CalculeazaPretFinal();
}LINQ (Language Integrated Query) este un set de metode integrate în C# care exprimă aceste operații concis, citibil și componabil. Varianta cu LINQ:
double total = bileteCumparate
.Where(b => b.EsteValid())
.Sum(b => b.CalculeazaPretFinal());Ambele variante produc același rezultat. Diferența este că varianta LINQ exprimă ce vrei, nu cum să obții. Codul descrie intenția, nu mecanica.
Metodele LINQ operează pe orice colecție care implementează IEnumerable<T>, ceea ce include List<T>, array-uri și orice altă colecție standard. Pentru a le utiliza, adaugi using System.Linq; la începutul fișierului.
Expresiile lambda în LINQ
Majoritatea metodelor LINQ primesc o funcție ca parametru: o instrucțiune aplicată fiecărui element al colecției. Această funcție se scrie inline cu sintaxa lambda: element => expresie. Mai multe detalii găsiți în cadrul Seminarului 3.
b => b.CalculeazaPretFinal()Se citește: pentru fiecare b, evaluează b.CalculeazaPretFinal(). b este parametrul. Numele este ales de tine și nu are nicio semnificație specială. Ce urmează după => este expresia evaluată pe fiecare element.
Lambda-urile nu sunt o sintaxă specială inventată pentru LINQ. Sunt funcții anonime care pot fi transmise ca argumente oriunde este așteptat un tip Func<T, TResult> sau Action<T>. LINQ le utilizează extensiv, dar le-am întâlnit deja în capitolul despre delegați.
Filtrare
Where
WhereWhere returnează elementele care satisfac o condiție. Primește un predicat (o funcție care returnează bool pentru fiecare element - detalii în Seminarul 3) și produce o colecție cu elementele pentru care predicatul este true:
OfType<T>
OfType<T>OfType<T> filtrează colecția după tipul real al obiectelor și returnează doar elementele de tipul T sau derivate din el. Spre deosebire de Where, nu verifică o condiție arbitrară, ci tipul runtime al fiecărui element:
OfType<T> este deosebit de utilă în contextul colecțiilor heterogene (liste de tipul clasei de bază care conțin obiecte de tipuri derivate diferite) și se combină natural cu metodele generice din CasaBilete:
Agregare
Metodele de agregare produc o singură valoare dintr-o colecție, aplicând o operație cumulativă pe toți membrii.
Sum
SumCalculează suma valorilor returnate de o expresie aplicată fiecărui element:
Count
CountNumără elementele colecției, opțional cu un predicat de filtrare:
Min, Max și Average
Min, Max și AverageReturnează valoarea minimă, maximă sau media aritmetică a unei expresii:
Sortare
OrderBy și OrderByDescending
OrderBy și OrderByDescendingSortează colecția crescător sau descrescător după o cheie specificată printr-un lambda:
ThenBy
ThenByAdaugă un criteriu secundar de sortare, aplicat când primul criteriu produce egalitate. Se înlănțuiește după OrderBy sau OrderByDescending:
Extragerea de elemente individuale
First și FirstOrDefault
First și FirstOrDefaultFirst returnează primul element al colecției sau primul care satisface un predicat. Aruncă excepție dacă colecția este goală sau dacă niciun element nu satisface condiția.
FirstOrDefault are același comportament, dar returnează null în loc să arunce excepție:
Alegerea între First și FirstOrDefault depinde de context: dacă absența elementului este o situație excepțională care trebuie semnalată ca eroare, First este potrivit. Dacă absența este o posibilitate normală, FirstOrDefault cu verificare null este mai adecvat.
Single și SingleOrDefault
Single și SingleOrDefaultSimilare cu First, dar verifică în plus că există exact un singur element care satisface condiția. Aruncă excepție dacă există mai multe:
Transformare
Select
SelectTransformă fiecare element dintr-o colecție într-o altă valoare, producând o nouă colecție cu rezultatele:
Lazy loading și ToList
ToListUn aspect important al LINQ este că metodele de filtrare și transformare returnează un IEnumerable<T> care este lazy loaded. Calculul efectiv are loc abia când parcurgi rezultatul, nu la momentul apelului metodei LINQ.
Aceasta înseamnă că înlănțuirea mai multor metode LINQ nu produce rezultate intermediare. Se construiește o descriere a transformărilor, care este executată o singură dată când datele sunt efectiv necesare.
Dacă ai nevoie de o colecție materializată imediat (de exemplu, pentru a o stoca sau a o parcurge de mai multe ori), apelezi ToList() sau ToArray() la finalul lanțului:
Fără ToList(), variabila bileteValide ar conține o interogare care se re-execută de fiecare dată când este parcursă, nu o listă calculată o singură dată.
Înlănțuirea metodelor
Metodele LINQ pot fi înlănțuite: rezultatul uneia devine intrarea celeilalte. Aceasta permite exprimarea unor interogări complexe într-un format citibil, fiecare linie adăugând un pas de transformare:
Se citește de sus în jos: din toate biletele, păstrează-le pe cele valide, sortează-le descrescător după preț, ia primele trei, materializează ca listă. Fiecare metodă exprimă o transformare precisă, iar înlănțuirea lor produce o interogare compozită clară.
Last updated