7. diel - LINQ v C# .NET - Revolúcia v dopytovaní
V predchádzajúcom cvičení, Riešené úlohy k 5.-6. lekcii práce s kolekciami v C# .NET, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.
V dnešnom tutoriáli C# .NET sa zameriame na technológiu LINQ, ktorá predstavuje súbor nástrojov na dopytovanie dát. Jedná sa o veľmi revolučnú technológiu, ktorá prácu s akýmikoľvek dátami zjednodušuje a zovšeobecňuje.
Motivácia
Určite sme všetci doteraz pracovali s rôznymi typmi kolekcií rôznym spôsobom. Inak sme hľadali prvok v poli, inak sme čítali dáta z XML súboru a inak by sme hľadali používateľa v databáze. Predstavte si ale keby existoval jeden unifikovaný spôsob, akým sa na dáta pýtať. Keby sme mohli ten istý dopyt spustiť ako na obyčajnom poli aj na XML súbore alebo databáze. Asi tušíte, že LINQ nám poskytuje presne takýto komfort. Ide o obrovskú abstrakciu, ktorá je vykúpená iba zanedbateľným znížením výkonu a ktorá dostala programovanie v C# do nových výšin.
LINQ ako jazyk
LINQ je pomerne sofistikovaná a rozsiahla technológia. Jej názov pochádza z anglického Language INtegrated Query. Ako názov napovedá, jedná sa o dopytovací jazyk, ktorý je integrovaný priamo do syntaxe jazyka C#. Je teda jeho súčasťou a to od C# 3.0 a .NET frameworku 3.5. Od novších verzií beží dokonca na viacerých vláknach, čo zvyšuje efektivitu tejto technológie.
LINQ je veľmi podobný jazyku SQL a je to teda jazyk deklaratívny. Programu oznámime, čo hľadáme a už nás veľmi nezaujíma, akým spôsobom pre nás dáta naozaj vyhľadá. Výhodou integrácie LINQ do C# je syntaktická kontrola dopytov pri preklade programu.
Uveďme si malý príklad skôr, ako pôjdeme ďalej. Vytvorte si nový projekt, pôjde o konzolovú aplikáciu s menom LINQ. Vytvoríme si jednoduché pole textových reťazcov:
string[] names = {"David", "Martin", "Daniel", "Peter", "John", "Elisa"};
Teraz si pomocou dopytu LINQ z tohto poľa vyberieme tie položky, ktorých dĺžka je väčšia ako 5 písmen. Do programu zapíšme nasledujúci kód:
var query = from n in names where (n.Length > 5) select n;
Dopyt nápadne pripomína SQL, tí ktorí ho poznajú, majú výhodu.
Myslím, že SQL nad poľom ste ešte nevolali, že?
Dopyt si hneď podrobne popíšeme,
najskôr ale dokončíme náš program a to tým, že si výsledok otázky
vypíšeme do konzoly:
{CSHARP_CONSOLE}
string[] names = {"David", "Martin", "Daniel", "Peter", "John", "Elisa"};
var query = from n in names
where (n.Length > 5)
select n;
// printing the result
foreach (string name in query)
{
Console.WriteLine(name);
}
Console.ReadKey();
{/CSHARP_CONSOLE}
Výstup programu:
Konzolová aplikácia
Martin
Daniel
Ako vyzerá dopyt
Vráťme sa k nášmu dopytu, ktorý vyzeral takto:
var query = from n in names where (n.Length > 5) select n;
Znalci SQL budú určite prekvapení, že je dopyt pospiatky. Má to svoje opodstatnenie, ku ktorému dôjdeme.
Najprv určujeme, odkiaľ budeme dáta vyberať, slúži na to kľúčové
slovo from. Za from nasleduje premenná, ktorá bude
vo zvyšku dopytu reprezentovať prvok z kolekcie. Ďalej nasleduje kľúčové
slovo in a samotná kolekcia. Je to podobné ako pri cykle
foreach. Dopyty sa píšu na niekoľko riadkov, aby boli
prehľadnejšie. To oceníte najmä u tých zložitejších.
Na opodmienkovanie môžeme uviesť riadok s kľúčovým slovom
where, za ním nasleduje podmienka. Podmienky v tomto prípade
píšeme úplne rovnako, ako sme to robili doteraz.
Na poslednom riadku nasleduje kľúčové slovo select, pomocou
ktorého určíme, čo vyberáme. Tu vyberáme celý prvok z kolekcie, teda
n. Rovnako tak by sme ale mohli vybrať napríklad len jeho dĺžku
n.Length alebo čokoľvek iné.
Kľúčové slovo var
Dopyt ukladáme do premennej typu var, s týmto typom sme sa
ešte nestretli a vlastne to ani dátový typ nie je. Kľúčové slovo
var nám umožňuje prenechať výber dátového typu na
kompilátore (rozumejte, že ho za nás priradí C# sám pri preklade).
Teoreticky by sme var mohli použiť aj inokedy, napr. takto:
var s = "C# will recognize that this is a string and it will assign the string type to the variable s"; var i = 10;
Kód vyššie C# preloží v podstate na toto:
string s = "C# will recognize that this is a string and it will assign the string type to the variable s"; int i = 10;
Kľúčové slovo var teda umožňuje určiť dátový typ až
pri preklade programu a vlastne nás od dátového typu odbremeňuje. V
bežných programoch by bolo var na obtiaž, pretože
špecifikácia typov má svoj zmysel. Typy teda budeme písať ďalej a v
žiadnom prípade ich nenahradzujte slovom var, ako sa to niektorí
začiatočníci úplne chybne naučia.
Kľúčové slovo var bolo zavedené kvôli LINQ, a to z troch
dôvodov:
- Po prvé, dátové typy dopytov sú pomerne zložité a bolo by komplikované ich vždy explicitne špecifikovať.
- Po druhé, keď zmeníme typ kolekcie, zmení sa aj typ dopytu, čo by vyžadovalo zbytočnú editáciu kódu a znížilo všeobecnosť technológie.
- Po tretie s LINQ prichádzajú tzv. anonymné typy, pri ktorých sa bez
klúčového
varnezaobídeme, čoskoro sa k nim dostaneme.
Zapamätajte si, že var má svoje miesto hlavne v
dopytoch a v bežnom kóde by sa nemal príliš vyskytovať, aj keď by
sa tam teoreticky dalo použiť.
Všeobecne platí poučka, že var môžeme použiť v prípade,
že zjednoduší deklaráciu a pritom je stále jasné, akého typu premenná
je. Ukážme si štyri príklady bez var a s var:
int a = 10; List<Dictionary<string, string>> dictionaries = new List<Dictionary<string, string>>(); IOrderedQueryable<User> fromNY = from u in db.Users where u.City == "New York" orderby u.Name select u; int b = a;
Pomocou var môžeme kód upraviť takto:
var a = 10; var dictionaries = new List<Dictionary<string, string>>(); var fromNY = from u in db.Users where u.City == "New York" orderby u.Name select u; int b = a;
Pri prvej premennej nám var neprinesie žiadne zjednodušenie.
Pri generickom liste generických slovníkov sa jeho použitie naopak oplatí a
keďže z pravej strany priradenia aj vidíme, akého typu je premenná
dictionaries, var je tu aj dobrou voľbou. Rovnako by
ale bolo čistejšie napísať na niečo podobné triedu, ukladať kolekcie
kolekcií je skôr zlá praktika. Pri dopyte LINQ je typ zložitý a keby sme
napr. zmazali orderby, zmenil by sa na
IQueryable<User>. Takto nemusíme nad typom premýšľať a
ani ho meniť spolu s dopytom. Posledné použitie var je
odstrašujúce, vôbec z riadka nepoznáme, čo do premennej b
ukladáme.
Ďalšou častou chybou je, že si ľudia myslia, že var
deklaruje premennú dynamického typu, teda že do nej môžeme uložiť, čo
chceme. Nie je to tak; typ sa napevno určí pri preklade a počas programu ho
nemožno meniť. Kód nižšie teda nebude fungovať:
// this code will not work var variable = "It contains text now"; variable = 10; // Now it contains a number
Program vyššie vytvorí premennú typu string a potom spadne,
pretože sa do typu string snažíme uložiť int.
Pod pokrievkou
Ako to vlastne celé funguje? Keď sa pozriete na začiatok vášho
zdrojového kódu, uvidíte, že obsahuje nasledujúci using:
using System.Linq;
Ten je prednačítaný pri všetkých typoch projektov. Skúsme ho
okomentovať a v tej chvíli nám Visual Studio podčiarkne v dopyte premennú
names.
LINQ funguje pomocou tzv. providerov, tých je niekoľko
typov a je aj možné definovať si vlastné. My teraz používame LINQ
To Objects, ktorý je implementovaný práve v mennom priestore
System.Linq a ktorý rozšíri obyčajné kolekcie ako sú napr.
polia a listy s ďalšími metódami navyše. Pod pokrievkou sú teda
rozširujúce metódy.
Skúsme si teraz (using majme stále ešte okomentovaný)
vyvolať ponuku metód na našom poli. Napíšme names.
(names a bodku), aby sme vyvolali zoznam metód na poli:

Teraz using opäť odkomentujme a urobme to isté znova:

Na obyčajnom poli máme zrazu kvantum nových metód. Keď C# vykonáva dopyt LINQ, volá na pozadí na kolekciu tieto metódy. Tie sú riešené cez lambda výrazy, s ktorými sme sa už stretli v kurze OOP.
Náš dopyt:
var query = from n in names where (n.Length > 5) select n;
C# spracuje a vygeneruje nasledujúci kód:
{CSHARP_CONSOLE}
string[] names = {"David", "Martin", "Daniel", "Peter", "John", "Elisa"};
var query = names.Where(n => n.Length > 5).Select(n => n);
// printing the result
foreach (string name in query)
{
Console.WriteLine(name);
}
Console.ReadKey();
{/CSHARP_CONSOLE}
Výstup:
Konzolová aplikácia
Martin
Daniel
Môžete si vyskúšať, že dopyt bude fungovať rovnako. Máme možnosť s
LINQ pracovať aj takto, ale pomocou SQL-like zápisu je to oveľa
stráviteľnejšie. Mimochodom, práve sme si vysvetlili, prečo je v dopyte
najprv where a až potom select. Dáta sa musia najprv
nájsť metódou Where() a z výsledku sa až potom označia, čo
nás zaujíma metódou Select(). Dôvodom je teda postupnosť
metód pod pokrievkou technológie.
Na záver si prezraďme, na čo sa v našom prípade preloží záhadný typ
var. Výsledný typ dopytu na našom poli je:
System.Linq.Enumerable.WhereArrayIterator<string>
Keďže nemôžeme z hlavy vedieť, aký typ LINQ práve vráti (presnejšie
povedané by sme od toho mali byť odbremenený), bol zavedený typ
var, ako už bolo povedané vyššie.
V nasledujúcej lekcii, LINQ providery, anonymné typy, radenie a zoskupovanie, budeme pokračovať v dopytovaní.
Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami
Stiahnuté 7x (59.01 kB)
Aplikácia je vrátane zdrojových kódov v jazyku C#

David sa informačné technológie naučil na