LINQ (Language Integrated Query)
V C# je LINQ pouze syntaxe, nedefinuje zadnou semantiku.
var collection : IEnumerable<int>;
// do klauzule pisu pouze telo lambda funkce
var data = from x in collection where x < 5 orderby x select x*2;
// prelozi se to na tohle
data == collection.Where(x => x<5).Orderby(x => x).Select(x => x*2);
To prvni se prelozi na to druhe. Standardne ty metody Where, Select, Orderby atd implementuje modul LinqToObjects. Ale muzu si napsat vlastni implementaci - jsou to proste extension metody IEnumerable, potom dam using a bude se pouzivat to moje.
Pokud se to spodni prelozi, tak to horni je syntakticky spravne. Dotaz musi zacinat from
, musi tam byt in
a musi koncit select
. Select funguje stejne jako ostatni klauzule, az na pripad, kdy vracim proste x
, pak se ten posledni Select(x => x)
vynecha.
Navrat LINQ vyrazu urcuje posledni klauzule, muze to byt cokoliv, takze se pouziva duck typing.
To x
nemusi byt collection, ale uplne cokoliv. Na tom typu jen musi existovat public metody odpovidajici tem klauzulim, co pouzivam.
To LinqToObjects implementuje extension metody pro IEnumerable ve staticke tride Enumerable
.
Pokud budu mit nejaky vlastni typ, ktery implementuje IEnumerable a definuju si na nem vlastni metodu Where
, tak se misto te LINQ metody pouzije ta moje.
Jak funguje LinqToObjects
- tato sekce je okopirovana z vitkolos.cz
- myšlenka LINQ to Objects – zavolání metod má jenom připravit
dotaz, nemá se to reálně dotazovat
- když zavolám Where, tak z ní vypadne krabička, kde je delegát a datový zdroj
- ta krabička se jmenuje třeba WhereEnumerable
- WhereEnumerable taky implementuje všechny metody z LINQu
- ale tady už se nepoužívají extension metody
- pokud zavolám Where na WhereEnumerable, tak si to nové WhereEnumerable bude jako zdroj pamatovat to původní WhereEnumerable
- výsledek LINQ to Objects dotazu vytvoří vázaný seznam těch krabiček
- všechny ty krabičky implementují rozhraní IEnumerable
- můžeme na tom udělat foreach cyklus
- při inicializaci dostaneme enumerátor poslední krabičky
- první volání MoveNext
- vygeneruje enumerátory všech krabiček v tom spojáku
- zkontroluje to platnost predikátů u Where klauzulí
- volá to MoveNext na enumerátorech, dokud nejsou predikáty splněny
- LINQ to Objects provádí líné vyhodnocení (lazy evaluation) dotazu
- ten dotaz nemusím enumerovat celý
- jeden dotaz můžu spouštět vícekrát (od začátku)
- dotazy můžu skládat
- pozor, když mám proměnnou se seznamem, nad kterou postavím LINQ krabičky, a do této proměnné uložím jiný seznam, tak se dotaz nepřegeneruje – má v sobě uložený odkaz na entitu, ne na proměnnou
- lazy evaluace se používá, pokud je to možné
- pro některé operátory se dělá eager evaluace
- Where … lazy
- OrderBy … eager
- ale to se týká jenom LINQ to Objects, neplatí to univerzálně
- u eager evaluace se data musí někam uložit
- C# překladač nedělá žádné optimalizace – je potřeba přemýšlet
nad pořadím klauzulí
- where → orderBy vs. orderBy → where
- nejspíš bude efektivnější ta první varianta (pokud where a orderBy fungují tak, jak bychom čekali)
- poznámka – LINQ to Objects dělá nějaké optimalizace
- pokud máme dvě Where za sebou, tak se to sloučí do jedné krabičky, která má seznam podmínek
- podobně Where a Select krabičky se můžou sloučit
- ale nemění to fungování