Lekce 7 - LINQ v C# .NET - Revoluce v dotazování
V předešlém cvičení, Řešené úlohy k 5.-6. lekci práce s kolekcemi v C# .NET, jsme si procvičili nabyté zkušenosti z předchozích lekcí.
V dnešním C# .NET tutoriálu se zaměříme na technologii LINQ, která představuje soubor nástrojů pro dotazování se na data. Jedná se o velmi revoluční technologii, která práci s jakýmikoli daty zjednodušuje a zobecňuje.
Motivace
Určitě jsme všichni dosud pracovali s různými typy kolekcí různým způsobem. Jinak jsme hledali prvek v poli, jinak jsme četli data z XML souboru a jinak jsme hledali uživatele v databázi. Představme si však, že by existoval jeden unifikovaný způsob, jakým by se dalo na data dotazovat. Že bychom mohli tentýž dotaz spustit jak na obyčejném poli, tak na XML souboru nebo databázi. Asi tušíte, že LINQ nám poskytuje přesně takový komfort. Jedná se o obrovskou abstrakci, která je vykoupena pouze zanedbatelným snížením výkonu a která vyhnala programování v C# do nových výšin.
LINQ jako jazyk
LINQ je poměrně sofistikovaná a rozsáhlá technologie. Její název je odvozený od anglického Language INtegrated Query. Jak název napovídá, jedná se o dotazovací jazyk, který je integrovaný přímo do syntaxe jazyka C#. Je tedy jeho součástí, a to od C# 3.0 a .NET frameworku 3.5. Od novějších verzí běží dokonce na více vláknech, což zvyšuje efektivitu této technologie.
LINQ je velmi podobný jazyku SQL, je to tedy jazyk deklarativní. Programu sdělíme, co hledáme, a už nás moc nezajímá, jakým způsobem program data pro nás opravdu vyhledá. Výhodou integrace LINQ do C# je syntaktická kontrola dotazů při překladu programu.
Než půjdeme dál, ukažme si malý příklad. Založme si nový projekt, půjde o konzolovou aplikaci jménem LINQ. Vytvoříme si jednoduché pole textových řetězců.
string[] jmena = {"David", "Martin", "Dan", "Petr", "Vratislav", "Eliška"};
Nyní si pomocí LINQ dotazu z tohoto pole vybereme ty položky, jejichž délka je větší než pět písmen. Do programu zapíšeme následující kód:
var dotaz = from j in jmena where (j.Length > 5) select j;
Dotaz nápadně připomíná SQL, proto mají výhodu ti, kteří ho znají.
Myslíme, že SQL nad polem jste ještě nevolali, že? Dotaz si hned podrobně popíšeme,
nejprve však dokončeme náš program, a to tím, že si výsledek dotazu
vypíšeme do konzole:
{CSHARP_CONSOLE}
string[] jmena = { "David", "Martin", "Dan", "Petr", "Vratislav", "Eliška" };
var dotaz = from j in jmena
where (j.Length > 5)
select j;
// výpis výsledku
foreach (string jmeno in dotaz)
{
Console.WriteLine(jmeno);
}
Console.ReadKey();
{/CSHARP_CONSOLE}
Výstup programu:
Konzolová aplikace
Martin
Vratislav
Eliška
Jak vypadá dotaz
Vraťme se k našemu dotazu, který vypadal takto:
var dotaz = from j in jmena where (j.Length > 5) select j;
Znalci SQL budou jistě překvapení, že je dotaz pozpátku. Má to své opodstatnění, ke kterému dojdeme.
Nejprve určujeme, odkud budeme data vybírat. K tomu slouží klíčové
slovo from
. Za from následuje proměnná, která bude ve zbytku
dotazu reprezentovat prvek z kolekce. Dále následuje klíčové slovo
in
a samotná kolekce. Je to podobné jako u cyklu
foreach
. Dotazy se píšou na několik řádků, aby byly
přehlednější. To oceníme zejména u těch složitějších.
Pro opodmínkování můžeme uvést řádek s klíčovým slovem
where
, za nímž následuje podmínka. Podmínky v tomto případě
píšeme úplně stejně, jako jsme to dělali doposud.
Na posledním řádku následuje klíčové slovo select
,
pomocí nějž určíme, co vybíráme. Zde vybíráme celý prvek z kolekce,
tedy j
. Stejně tak bychom ale mohli vybrat třeba jen jeho délku
j.Length
nebo cokoli jiného.
Klíčové slovo var
Dotaz ukládáme do proměnné typu var
. S tímto typem jsme se
ještě nesetkali a vlastně to ani datový typ není. Klíčové slovo
var
nám umožňuje přenechat výběr datového typu na kompileru
(tedy, že ho za nás přiřadí C# sám při překladu). Teoreticky bychom
var
mohli použít i jindy, např. takto:
var s = "C# pozná, že toto je string, a dá proměnné s typ string"; var i = 10;
C# kód výše přeloží v podstatě na toto:
string s = "C# pozná, že toto je string, a dá proměnné s typ string"; int i = 10;
Klíčové slovo var
tedy umožňuje určit datový typ až při
překladu programu a vlastně nás od datového typu odstiňuje. V běžných
programech by bylo slovo var
na obtíž, jelikož specifikace typů
má svůj smysl. Typy tedy budeme psát dále a v žádném případě je
nenahrazujte slovem var
, jak se to někteří začátečníci zcela
chybně učí.
Klíčové slovo var
bylo zavedeno kvůli LINQ, a to ze tří
důvodů:
- Datové typy dotazů jsou poměrně složité a bylo by komplikované je vždy explicitně specifikovat.
- Když změníme typ kolekce, změní se také typ dotazu, což by vyžadovalo zbytečnou editaci kódu a snížilo by to obecnost technologie.
- S LINQ přicházejí tzv. anonymní typy, u kterých se bez
var
neobejdeme. Brzy se k nim dostaneme.
Pamatujme si, že var
má své místo hlavně v dotazech
a v běžném kódu by se neměl příliš vyskytovat, i když by se
tam teoreticky dalo použít.
Obecně platí poučka, že var
můžeme použít v případě,
že zjednoduší deklaraci, ale přitom je stále jasné, jakého typu
proměnná je. Ukažme si čtyři příklady bez var
a s
var
:
int a = 10; List<Dictionary<string, string>> slovniky = new List<Dictionary<string, string>>(); IOrderedQueryable<Uzivatel> prazane = from u in db.Uzivatele where u.Mesto == "Praha" orderby u.Jmeno select u; int b = a;
Pomocí var
můžeme kód upravit takto:
var a = 10; var slovniky = new List<Dictionary<string, string>>(); var prazane = from u in db.Uzivatele where u.Mesto == "Praha" orderby u.Jmeno select u; var b = a;
U první proměnné nám var
nepřinese žádné zjednodušení.
U generického listu generických slovníků se jeho použití naopak vyplatí,
a jelikož z pravé strany přiřazení také vidíme, jakého typu je
proměnná slovniky
, var
je zde i dobrou volbou.
Přesto by však bylo čistší napsat na něco podobného třídu, neboť
ukládat kolekce kolekcí je spíše špatná praktika. U LINQ dotazu je typ
složitý, a pokud bychom např. odmazali orderby
, změnil by se na
IQueryable<Uzivatel>
. Takto nemusíme nad typem přemýšlet
ani ho měnit spolu s dotazem. Poslední užití slova var
je
odstrašující. Z řádky vůbec nepoznáme, co do proměnné b
ukládáme.
Další častou chybou je, že si lidé myslí, že var
deklaruje proměnnou dynamického typu, tedy že do ní můžeme uložit, co
chceme. Avšak není tomu tak. Typ se napevno určí při překladu a během
programu ho nelze měnit. Kód níže tedy nebude fungovat:
// tento kód nebude fungovat var promenna = "Nyní tam je text"; promenna = 10; // Nyní tam je číslo
Program výše vytvoří proměnnou typu string
a poté spadne,
protože se do typu string
snažíme uložit int
.
Pod pokličkou
Nyní si vysvětlíme, jak to celé funguje. Když se podíváme na začátek
našeho zdrojového kódu, uvidíme, že obsahuje následující
using
:
using System.Linq;
Ten je přednačtený u všech typů projektů. Zkusme ho zakomentovat. V tu
chvíli nám Visual Studio podtrhne v dotazu proměnnou jmena
.
LINQ funguje pomocí tzv. providerů. Těch je několik
typů a je také možné definovat si vlastní. My nyní používáme
LINQ To Objects, který je implementován právě ve jmenném
prostoru System.Linq
a který rozšíří obyčejné kolekce, jako
jsou např. pole a listy, o další metody navíc. Pod pokličkou jsou tedy
rozšiřující metody.
Zkusme si nyní (ještě se zakomentovaným using
) vyvolat
nabídku metod na našem poli. Napišme jmena.
(jmena
a tečku), abychom seznam metod na poli vyvolali:

Nyní řádek opět odkomentujme a udělejme totéž znovu:

Na obyčejném poli máme najednou kvantum nových metod. Když C# vykonává LINQ dotaz, volá na pozadí na kolekci tyto metody. Ty jsou řešené přes lambda výrazy, s nimiž jsme se již setkali v OOP kurzu.
Náš dotaz…
var dotaz = from j in jmena where (j.Length > 5) select j;
… tedy C# zprocesuje a vygeneruje následující kód:
{CSHARP_CONSOLE}
string[] jmena = { "David", "Martin", "Dan", "Petr", "Vratislav", "Eliška" };
var dotaz = jmena.Where(j => j.Length > 5).Select(j => j);
// výpis výsledku
foreach (string jmeno in dotaz)
{
Console.WriteLine(jmeno);
}
Console.ReadKey();
{/CSHARP_CONSOLE}
Výstup:
Konzolová aplikace
Martin
Vratislav
Eliska
Můžete si vyzkoušet, že dotaz bude fungovat stejně. Máme možnost s
LINQ pracovat i takto, ale pomocí SQL-like zápisu je to mnohem
stravitelnější. Mimochodem, právě jsme si vysvětlili, proč je v dotazu
nejprve where
a až poté select
. Data se musí
nejprve najít metodou Where()
a z výsledku se teprve poté
označí metodou Select()
, co nás zajímá. Důvodem je tedy
posloupnost metod pod pokličkou technologie.
Na závěr si prozraďme, na co se v našem případě přeloží onen
záhadný typ var
. Výsledný typ dotazu na našem poli je:
System.Linq.Enumerable.WhereArrayIterator<string>
Jelikož nemůžeme z hlavy vědět, jaký typ LINQ zrovna vrátí
(přesněji řečeno bychom od toho měli být odstíněni), byl zaveden právě
typ var
, jak již bylo řečeno výše.
V příští lekci, LINQ providery, anonymní typy, řazení a seskupování, budeme pokračovat v dotazování.
Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 604x (23.17 kB)
Aplikace je včetně zdrojových kódů v jazyce C#