Tales from under the Mountain

July 2008 - Posts

Lambda izrazi

Posle dužeg vremena neaktivnosti, vreme je da se opet pokrene pisanje na ovom blogu. Imam dva posta koji čekaju u redu već neko vreme.

Delegati

Krenimo od osnova. Da bi smo razumeli šta su lambda izrazi, prvo moramo da se podsetimo šta su to delegati. Kao što nam govori MSDN dokumentacija, delegati su:

A delegate is a type that references a method. Once a delegate is assigned a method, it behaves exactly like that method.

Isto tako, delegat se može i kratko definisati kao:

Type-safe function pointer.

U prevodu, delegati su prosto pokazivači na određene funkcije, odnosno metode, koji su tipizirani. U delegat možemo da smestimo metod koji se slaže u potpisu sa delegatom, i kasnije da taj isti metod pozovemo. Naravno, jedan metod po delegatu je malo ograničen, te imamo i multicast delegates, koji su isto (makar za potrebe ove diskusije) kao i obični delegati, osim što u sebi mogu da sadrže više metoda.

Da bismo dobili osećaj za korišćenje delegata, pogledajmo jedan prostr primer sa (mojim omiljenim) FindAll metodom List objekta. Taj metod služi da filtrira listu po određenom predikatu, u ovom slučaju to je delegat koji će imati za ulogu da nađe sve parne brojeve.

   1: class Program {
   2:     static void Main(string[] args) {
   4:         List<int> l = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
   5:         System.Predicate<int> example = new Predicate<int>(someMethod);
   6:         var result = l.FindAll(example);
   7:         foreach (var item in result) {
   8:             Console.WriteLine(item);
   9:         }
  10:              
  11:    }
  12:  
  13:     public static bool someMethod(int i){
  14:         return i%2==0;
  15:     }
  16:                 
  17:  
  18: }

System.Predicate koji koristim ovde je generički tip koji predstavlja delegat. U njega možemo da smestimo metod koji vraća bool vrednost, a prima parameter tipa integer. Zatim prosleđujemo predikat (delegat) metodu FindAll, i dobijamo našu listu parnih brojeva. Metod FindAll očekuje tip predikata, i sve je super. Kao rezultat se dobija lista parnih brojeva, koju potom ispisujemo.

Naravno, ovo je trivijalni primer korišćenja delegata. Posetioci ovog bloga koji su puno radili sa Windows Forms aplikacijama sigurno znaju za InitializeCompontent() metod koji u sebi sadrži definicije i postavljanje paramterara svih kontrola koje ste izvukli na površinu za dizajn tokom razvoja, kao i seriju izraza kojima se dodaju event handlers, koji su u stvari prosti delegati određenog potpisa.

Anonimni delegati

U .NET Framework verziji 2.0 uveden je koncept anonimnih delegata, koji uvode mnogo čistiju sintaksu i dozvoljavaju nam da kreiramo delegat "na licu mesta" (ilit ad hoc, kako je mnogo fensije da se kaže). Sintaksa je prosta, i sastoji se od bloka i ključne reči delegate. Gornji primer bi mogao da se napiše uz pomoć anonimnih delegata na sledeći način:

 

   1: class Program {
   2:     static void Main(string[] args) {
   4:         List<int> l = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
   5:         System.Predicate<int> example = new Predicate<int>(delegate(int i) { return i % 2 == 0; });
   6:         var result = l.FindAll(example);
   7:         foreach (var item in result) {
   8:             Console.WriteLine(item);
   9:         }
  10:              
  11:    }

 

U liiniji 5, umesto da u delegat smeštamo metod somemethod, kreiramo delegat na licu mesta, i prosleđujemo njega. Ostatak koda je potpuno isti, i rezultat koji se dobija korišćenjem anonimnih delegata je identitčan. Razlika je u tome što u ovom slučaju ostavljamo kompajleru da kreira potpis i metod. Naravno, pošto FindAll metod tipa List očekuje predikat, i možemo i dodatno da skratimo kod koji pišemo i da kreiramo anonimni delegat direktnu po pozivu metodi FindAll:

   1: var result = l.FindAll(delegate(int i) { return i % 2 == 0; });

Kod je...baš skraćen. :) U suštini, sve tri varijante rade iste stvari, samo je pitanje dužine koda koju morate da pišete.

Lambda izrazi

Lambda izrazi su evolucija anonimnih delegata. Pošto je .NET Framework 3.5 zasnovan na CLR-u 2.0, lambda izrazi predstavlju syntatc sugar, ali sa jako monim posledicama i namenama. Lambda izrazi se pišu na veoma prost način:

   1: System.Predicate<int> example = x => x % 2 == 0;
 
Pošto su lambda izrazi isto što i delegati, ova linija koda radi upravo ono što mislite da radi: dodeljuje delegat u generički tip System.Predicate. Kompajler će sam razrešiti lambda izraz, pretvoriti ga u delegat i dodeliti ga ovom tipu, bez da mi moramo da razmišljamo o tome. Sa naše strane, dobićemo
 
Sintaksička pravila su jednostavna. Sve što je sa leve strane je parametera, a sve sa desne strane "crtice" (=>) je telo metoda. U gornjem izrazu, x je parametar koji prosleđujemo lambda izrazu/anonimnom metodu, dok je "X % 2 == 0" izraz, odnosno telo metoda. Naravno, u ovom primeru imamo samo jedan parametar, ali ako želimo da prosledimo dva, prosto ćemo ih staviti u zagradu:
 
   1: var result = (x, y) => x * y;
 
Zašto su lambda ozrazi uopšte uvedeni? Prvo, da bi se dobila čistija i kraća sintaksa kada se pišu LINQ upiti. Setimo se da su LINQ operatori samo ekstenzije na bilo kom tipu koji implementira IEnumerable (ili IQuearyble). Postoji dva načina da radimo sa LINQ operatorima. Jedan je kroz korišćenje tzv. query expressions a drugi je kroz korišćenje metoda. Prvi način, ako pratimo gornji primer sa listom:
   1: var result = from i in l
   2:                 where i % 2 == 0
   3:                 select i;
   4: foreach (var item in result){
   5:     Console.WriteLine(item);
   6: }

Naravno, ukoliko se odlučimo za metode, onda rešenje postaje:

   1: var result = l.Where(i => i % 2 == 0);
   2: foreach (var item in result) {
   3:     Console.WriteLine(item);
   4: }

Naravno, i ovde postoji jedan trik. Zavisno od LINQ dijalekta koji koristimo, lambda izrazi će se koristiti kao delegati ili će biti pretvoreni u drugi oblik poznat kao expression trees.

Expression trees - ukratko

Rečeno u jednoj rečenici, expresson trees predstavljaju implementaciju koncepta AST-ova (abstract syntax trees), i služe da se deo koda ne tretira kao izvršni kod, već kao podaci. Ovaj koncept, poznat iz drugih jezika, uglavnom dinamičnih (na prvom mestu LISP-a) je užasno moćan, i otvara vrata novim mogućnostima. Ukoliko lambda izraz dodelite ne delegatu, već gorepomenutno drvetu, kompajler neće pretvoriti taj lambda izraz u IL, već će ga čuvati kao niz objekata tipa expression tree. Više o ovom konceptu i zašto uopšte postoji podrška za ovako nešto ćemo pokriti u sledećem blog postu.

Zaključak

Kratko sam pokrio lambda izraze, jedan od dodataka C# i VB.NET jeziku u verziji 3.0 i 9.0 respektivno. Kao što smo videli, lambda izrazi su, u suštini, nov način za pisanje starih i poznatih delegata, ali na način koji je mnogo kompaktniji. Početna podrška se javila već u verziji 2.0 .NET Framework-a, sa podrškom za anonimne delegate.

BD.

Posted: Jul 06 2008, 12:48 AM by blackdwarf | with no comments
Filed under: