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 doteď 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 bychom hledali uživatele v databázi. Představte si ale, kdyby existoval jeden unifikovaný způsob, jakým se na data dotazovat. Kdybychom mohli ten samý 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 pochází z 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 a je to tedy jazyk deklarativní. Programu sdělíme co hledáme a už nás moc nezajímá, jakým způsobem pro nás data opravdu vyhledá. Výhodou integrace LINQ do C# je syntaktická kontrola dotazů při překladu programu.
Udělejme si malý příklad, než půjdeme dál. Založte si nový projekt, půjde o konzolovou aplikaci se 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ž 5 písmen. Do programu zapište následující kód:
var dotaz = from j in jmena where (j.Length > 5) select j;
Dotaz nápadně připomíná SQL, ti kteří ho znají mají výhodu. Myslím, že SQL nad polem jste ještě nevolali, že? Hned si dotaz podrobně popíšeme, nejprve ale 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řekvapeni, že je dotaz pozpátku. Má to své opodstatnění, ke kterému dojdeme.
Nejprve určujeme odkud budeme data vybírat, slouží k tomu 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íší na několik řádků, aby byly
přehlednější. To oceníte 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í kterého 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
(rozumějte, ž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;
Kód výše C# 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 var
naobtíž, 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ě naučí.
Klíčové slovo var
bylo zavedeno kvůli LINQ a to ze třech
důvodů:
- Zaprvé jsou datové typy dotazů pomerně složité a bylo by komplikované je vždy explicitně specifikovat.
- Zadruhé když změníme typ kolekce, změní se i typ dotazu, což by vyžadovalo zbytečnou editaci kódu a snížilo obecnost technologie.
- Zatřetí s LINQem přichází tzv. anonymní typy, u kterých se bez var neobejdeme, brzy se k nim dostaneme.
Co si pamatujte je, ž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 a přitom je stále jasné jakého typu proměnná
je. Ukažme si 4 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í i vidíme jakého typu je proměnná
slovniky
, var
je zde i dobrou volbou. Stejně by ale
bylo čistší napsat na něco podobného třídu, ukládat kolekce kolekcí je
spíše špatná praktika. U LINQ dotazu je typ složitý a kdybychom např.
odmazali orderby
, změnil by se na
IQueryable<Uzivatel>
. Takto nemusíme nad typem přemýšlet
a ani ho měnit spolu s dotazem. Poslední užití var
je
odstrašující, vůbec z řádky 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. 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 = "Teď tam je text"; promenna = 10; // Teď tam je číslo
Program výše vytvoří proměnnou typu string
a poté upadne,
protože se do typu string
snažíme uložit int
.
Pod pokličkou
Jak že to celé funguje? Když se podíváte na začátek vašeho
zdrojového kódu, uvidíte, ž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 i možno si definovat 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
em) vyvolat
nabídku metod na našem poli. Napišme jmena.
(jmena
a tečku)., abychom vyvolali seznam metod na poli:
Nyní řádek opět odkomentujme a udělejme to samé 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, které jsme již potkali v OOP kurzu.
Náš dotaz:
var dotaz = from j in jmena where (j.Length > 5) select j;
tedy C# přežvýká 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čí co nás zajímá metodou Select()
. 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 typ
var
, jak již bylo řečeno výše.
V příští lekci, LINQ provideři, 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 600x (23.17 kB)
Aplikace je včetně zdrojových kódů v jazyce C#