Publikoval Michal Kočí dňa 30.11.2007 o 11:33 v kategórii .Net
Lambda výrazý sú novým prvkom jazyka c# a preto je vhodné, aby ich každý programátor poznal. Ak by ich aj hneď nechcel používať, tak aspoň preto, aby rozumel kódu ktorý vytvoril inž programátor. Poďme sa teda pozrieť na to, čo sú lambda výrazy, čo sa s nimi dá dosiahnuť a či a ako sa to isté dalo dosiahnuť v predchádzajúcich verziach. A tiež sa poďme pozrieť, aké ďaľšie nemenej zaujímavé vlastnosti nás v c# 3.5 čakajú.
Čo sú lambda výrazy
Lambda výrazy sú jednoducho anonymné funkcie, ktoré sa používajú najmä v súvislosti s technológiou LINQ, ale použiť sa dajú všade tam, kde treba použiť delegátov. Lambda výrazy vysvetlím na jednoduchom príklade a to na príklade použitia funkcie FindAll generickej kolekcie List<>, ktorá očakáva generického delegáta type Predicate<>.
V príklade nižšie budeme pracovať s kolekciou zákazníkov, kde typ Customer je deklarovaný nasledovne:
class Customer { public int Id { get; set; } public string Name { get; set; } public bool IsNew { get; set; } }
Všimnite si prvú novinku - automatické vlastnosti (Automatic properties). Ak máte vlastnosti, ktoré iba čítajú/zapisujú do premennej bez nejake ďaľšej funkcionality, môžete použiť automatickú vlastnosť (property), čo je vlatnosť, pre ktorú kompilátor automaticky vygeneruje premennú (nazvanú Backing field) s náhodným menom, ku ktorej však prístup nemáte. K premennej teda pristupujete iba cez vlastnosť čo však naozaj v prípade jednoduchého čítania a zapisovania stačí. Čiže vyššie uvedený príklad je identický s týmto (s tým rozdielom že k premenným máte prístup iba cez vlastnosti):
class Customer { private int id; public int Id { get { return id; } set { id = value; } } private string name; public string Name { get { return name; } set { name = value; } } private bool isNew; public bool IsNew { get { return isNew; } set { isNew = value; } } }
No a potrebujeme ešte zoznam zákazníkov, pre tento jednoduchý príklad si vytvoríme a naplníme kolekciu zákazníkov priamo v kóde:
var customers = new List<Customer> { new Customer() { Id=1, Name="John Smith", IsNew=true }, new Customer() { Id=2, Name="Amy Bush", IsNew=true }, new Customer() { Id=3, Name="Mark Gates", IsNew=false } };
V kóde je vidno ďaľšie tri nové vlastnosti jazyka c#:
Prvou z nich je klúčové slovo var. To hovorí kompilátoru, že deklarujeme premennú, ktorej typ sa dá zistiť veľmi jednoducho (podľa výrazu na pravej strane, za znamienkom "rovná sa") a že teda typ nebudeme vypisovať. Treba si uvedomiť jednu vec a to, že typ je známy už v dobe kompilácie a preto je stále zachovaná silný typovosť. A akonáhle je takto premenná zadeklarovaná, tak už nie je možné jej typ meniť. Čiže sa nejedná o podobnosť s JavaScriptom, kde toto klúčové slovo znamená iba to, že deklarujeme premennú. V c# to znamená, len a len to, že nevypisujeme typ premennej, nakoľko sa dá odvodiť. Vo vyššie uvedenom príklade teda premenná customers bude len a len kolekcia zákazníkov (List<Customer>). Teda použitím klúčového slova var sa dá toto:
List<Customer> customers = new List<Customer>();
zapísať skrátene takto:
var customers = new List<Customer>();
Druhou je inicializácia objektu (Object initializer). Zase nejde o nejakú revolučnú zmenu ale len o iný spôsob ako nainicializovať vlastnosti objektu. Vezmime si vyššie uvedený príklad inicializácie vlastností objektu Customer:
new Customer() { Id=1, Name="John Smith", IsNew=true }
Je to vlastne len iný, skrátený, zápis pre:
Customer c = new Customer(); c.Id = 1; c.Name = "John Smith"; c.IsNew = true;
A treťou demonštrovanou novinkou je inicializácia kolekcie (Collection inicializer). Je to zase len skrátený kód, kedy odpadá nutnosť pridávať prvky do kolekcie metódou Add. Na pozadí, t.j. v skompilovanom kóde, sa však naďalej nič iné nedeje. Kompilátor kód preloží presne tak, ako sa volakedý písal - tak, že volá metódu Add. Čiže kód:
List<Customer> customers = new List<Customer>(); customers.Add(c1); customers.Add(c2); customers.Add(c3);
sa dá teraz zapísať skrátene ako
List<Customer> customers = new List<Customer> { c1, c2, c3 };
Keď to teda zhrnieme, tieto štyri nové vlastnosti a ako uvidíme nižšie aj lambda výrazy (čiže 5 nových vlastností) nám výrazne šetrí počet riadkov kódu, ktoré musíme zapísať na relatívne často používané konštrukcie a zároveň aj sprehľadňuje kód. Čo podľa mňa nie až tak sprehľadňuje kód je použitie var ale aj to iba za predpokladu, že kód čítame cez iný editor ako je Visual Studio (napríklad Notepad). Pri prehliadaní kódu cez Visual Studio nám intellisense automaticky zobrazí správny typ a tým pádom aj ponúkne správne prvky takéhoto objektu.
No, posuňme sa ďalej. Kolekciu máme, máme ju aj naplnenú a majme jednoduchú úlohu. Vypísať do konzoly všetkých nových zákazníkov (t.j. tých, ktorý majú vlastnosť IsNew rovnú true). Použijeme na ich získanie metódu FindAll, ktorá požaduje delegáta typu Predicate<>, v našom prípade Predicate<Customer>. Poďme sa teda pozrieť, ako sa toto spraví po starom.
Klasické vytvorenie delegáta
Úplne najstarší a najklasickejší spôsob je, vytvoriť metódu, ktorá bude slúžiť ako delegát. Delegát Predicate v tomto prípade stanovuje, že metóda musí prijímať jeden parameter typu Customer a vracať hodnotu typu bool. Jej implementácia teda bude vyzerať nasledovne:private bool IsNewCustomer(Customer c) { return c.IsNew; }
A metódu FindAll potom budeme volať z metódy ClassicDelegate nasledovne:
private void ClassicDelegate(List<Customer> customers) { var newCustomers = customers.FindAll(IsNewCustomer); PrintCustomers("Classic Delegate", newCustomers); }
Vidíme teda, že musíme naimplementovať ďaľšiu metódu, ktorá implementuje delegáta. Ale nie vždy sa nám chce a najmä nie v takomto jednoduchom prípade ju implementovať. Nedá sa to jednoduchšie?
Použitie anonymných metód
Dá sa to jednoduchšie. Od c# 2.0 máme k dispozícii anonymné metódy, ktoré nám umožňujú, aby sme kód delegáta zapísali priamo tam, kde sa delegát očakáva a nemuseli sme vytvárať novú metódu. Ten stý prípad prepísaný pomocou anonymných metód vyzerá nasledovne:
private void AnonymousDelegate(List<Customer> customers) { var newCustomers = customers.FindAll( delegate(Customer c) { return c.IsNew; } ); PrintCustomers("Anonymous delegate", newCustomers); }
Výhody sú jasné - netreba vytvárať ďaľšiu metódu a hlavne kód delegáta je v tom mieste, kde sa bude volať a preto aj čítanie zdrojového kódu je jednoduchšie. Napriek tomu je ten zápis ešte trochu rozťahaný, nedá sa to ešte jednoduchšie?
Použitie lambda výrazov
Veruže hej, dá sa to ešte jednoduchšie. Od najnovšej verzie c# sa to dá ešte jednoduchšie a to vďaka lambda expression (výrazom). Totiž, keď sa ide písať anonymná metóda, tak sa obvykle niekam priraďuje, alebo priamo do delegáta alebo ako parameter metódy, kde sa zase pretaví do delegáta. A u delegáta je presne stanovené, akého typu majú byť argumenty, prečo potom ich typy špecifikovať znova?
Pre Lambda Expression existuje nový operátor =>, ktorý sa číta ako "ide do". Pred šípkou sa deklarujú premenné (v zátvorke; zátvorka je nepovinná iba ak má metóda práve jeden vstupný parameter a nešpecifikuje sa jeho typ) a uvedenie ich typu je nepovinné (typ sa špecifikuje len keď je nejasný). Za šípkou je výraz, ktorého hodnota sa vráti. Náš príklad sa dá pomocou Lambda Expression zapísať takto:
private void LambdaExpression(List<Customer> customers) { var newCustomers = customers.FindAll(c => c.IsNew); PrintCustomers("Lambda Expression", newCustomers); }
Ešte kratší zápis a ešte prehladnejší. A to je aj cieľ. Totiž, rovnako ako u všetkých predchádzaúcich nový vlastností jazyka c#, aj u tejto sa jedná len a len o novú syntax. Kompilátor totiž preloží kód podobne ako keby ste ho zapísali po starom.
Tak čo vravíte, zdajú sa Vám novinky v jazyku prínosné, alebo považujete ich prítomnosť za kontraproduktívnu? Mne osobne sa zdá, že naozaj veľmi sprehľadňjú kód a šetria programátorovi čas, čo myslíte?
Ak nechceš premeškať príspevky ako je tento, sleduj ma na Twitteri, alebo ak máš RSS čítačku, môžeš sledovať môj RSS kanál.