Espressioni Lambda


Livello: Intermedio


Premessa
:

Anche se l'utilizzo delle Espressioni Lambda è vario, riescono a dare il loro meglio quando sono utilizzate insieme agli "Extension Methods" di LINQ, per formare delle query dinamiche. Cercherò, quindi, dopo una panoramica, di spiegare come poterle utilizzare proprio in tal senso.


Cosa sono:

questo è quanto possiamo trovare nella definizione di Microsoft stessa:
Le "Espressioni Lambda" sono delle Funzioni senza nome che calcolano e restituiscono un valore. Esse possono essere utilizzate anche con i Tipi Delegati validi.
Cerchiamo, quindi, di spiegare quanto definito da Microsoft, per farlo iniziamo subito a vedere come si rappresenta un' Espressione Lambda:
codice:
Function (parametri) Risultato
Come possiamo vedere, è possibile suddividerla in 3 sezioni:

Function - Parola chiave per indicare che si tratta di una Funzione, ma come possiamo vedere essa è, volutamente ed obbligatoriamente, senza Nome.

parametri -Tutti i parametri devono avere tipi di dati specificati oppure tutti devono essere dedotti. Vedremo più avanti che quando un'Espressione Lambda viene assegnata ad un Delegato, essa può ricavare il Tipo di dato, dei parametri, direttamente dalla dichiarazione del Delegato (quindi verranno dedotti).

Risultato - Il risultato è a tutti gli effetti una singola espressione che tale funzione dovrà elaborare ed ha la particolarità che:
  • il valore restituito da questa espressione, sarà il valore restituito alla funzione chiamante
  • non avendo nessuna clausula As nella Funzione, il Tipo generato dall'espressione risulterà essere anche il Tipo che tale Funzione ritornerà

Possiamo anche notare che non è presente nè l'istruzione Return, nè l'istruzione End Function.


Come funzionano:

Prendiamo ad esempio la seguente Lambda Expression per determinare se un numero è di Tipo Int32.
codice:
Function(obj As Object) TypeOf obj Is Int32
Che potremmo vedere come:
codice:
| Function | (obj As Object) | TypeOf obj Is Int32 |
ParolaChiave    Parametri           Risultato
Questo ci dice che la Funzione restituirà un valore Booleano ed accetta come parametri un unico pametro di Tipo Object.
Ci dice anche che sarà possibile utilizzare tale Espressione solo dov'è richiesto un valore Booleano di ritorno, molto frequente per esempio nelle Estensioni .Where di LINQ.

Se dobbiamo utilizzare una stessa Espressione Lambda più volte, possiamo assegnarla ad una variabile, cosi da non doverla ridigitare ogni volta.
codice:
Dim CheckInt32 = Function(obj As Object) TypeOf obj Is Int32
Facendo una prova pratica, stampando a video il risultato di ritorno, possiamo scrivere
codice:
Dim CheckInt32 = Function(obj As Object) TypeOf obj Is Int32
Dim a As Single = 1
Dim b As Int32 = 1
Console.WriteLine("a is Int32: {0}", CheckInt32(a))
Console.WriteLine("b is Int32: {0}", CheckInt32(b))
Ma lo stesso risultato potevamo ottenerlo scrivendo
Dim a As Single = 1
Dim b As Int32 = 1
Console.WriteLine("a is Int32: {0}", (Function(obj As Object) TypeOf obj Is Int32)(a))
Console.WriteLine("b is Int32: {0}", (Function(obj As Object) TypeOf obj Is Int32)(b))
In quest'ultimo caso, possiamo notare come sia possibile Dichiarare ed eseguire contemporaneamente l'Espressione Lambda, basta racchiudere tra parentesi tonde la dichiarazione e far seguire con altre parentesi tonde il contenuto passato.

Facciamo un altro piccolo esempio generico
codice:
Dim Iva = Function(num As Integer) (num * 20) / 100
Dim a As Integer = 100
Console.WriteLine("Imponibile {0} Iva {1}", a, Iva(a))
Oppure
Dim a As Integer = 100
Console.WriteLine("Imponibile {0} Iva {1}", a, (Function(num As Integer) (num * 20) / 100)(a))


Tipi Delegati Validi
:

Come detto all'inizio, le Espressioni Lambda, possono essere assegnate a dei Delegati, e quando succede, è possibile omettere il Tipo di dato passato come parametro, perchè verrà dedotto dalla dichiarazione del Delegato stesso.

Il Delegato Generico più ricorrente nelle Estensioni di LINQ è il Delegato Generico Func, con cui è possibile passare 0 o più parametri e ricevere un valore di ritorno.
Func(Of TResult)
Func(Of T, TResult)
Func(Of T1,T2, TResult)
Etc..
Prendiamo ad esame il Delegato Func(Of T, TResult), per effettuare delle prove che prevedono il passaggio di 1 solo parametro.

Vediamo come viene dichiarato un Delegato a cui è assegnata una Espressione Lambda e come quest'ultima deduca i Tipi passati senza necessità di specificarli.

Si prenda in esame

codice:
Dim CheckMod2 As Func(Of Integer, Boolean) = Function(num) (num Mod 2) = 0
Come possiamo vedere, il primo parametro (Of T) del Delegato Func, corrisponde al parametro passato nella Espressione Lambda, e quest'ultima non ha specificato il tipo di parametro passato perchè lo ricava dalla dichiarazione del Delegato stesso (quindi deduce che num è di tipo Integer), questo ci permetterà di trattare num come un Tipo Integer anche se non lo abbiamo specificato direttamente; il secondo parametro (Boolean)(TResult) indica il Tipo che l'Espressione dovrà ritornare, ed infatti analizzando la sezione "Risultato" della nostra Espressione, essa ritornerà proprio un valore Booleano.
Quindi abbiamo creato un Delegato che dato un numero in ingresso, ci ritorna un valore Booleano che ci permette di stabilire se quel determinato numero diviso 2 ha un resto di 0.

Il suo Utilizzo
codice:
Dim CheckMod2 As Func(Of Integer, Boolean) = Function(num) (num Mod 2) = 0
Dim a As Integer = 1
Dim b As Integer = 2
Console.WriteLine("a Mod 2 = 0 : {0}", CheckMod2(a))
Console.WriteLine("b Mod 2 = 0 : {0}", CheckMod2(b))



Lambda Expressions in LINQ
:

Vediamo ora come sfruttare le potenzialità delle Espressioni Lambda con le Estensioni di LINQ, per realizzare delle Query dinamiche.

Prendiamo come esempio un' Estensione che abbia come parametro passato un Delegato Func(Of T, TResult), cosi da portare avanti l'esempio precedente.

Analizziamo l'estensione
.Where(predicate As Func(Of T, TResult)) As IEnumerable (Of T)
che ci permette di filtrare degli elementi in base ad un Predicato.

Un esempio con Espressione Lambda dichiarata preventivamente è il seguente
codice:
Dim Numeri As Integer() = {1, 2, 3, 4, 5, 6, 7, 8, 9}

Dim Predicate As Func(Of Integer, Boolean) = Function(num) (num Mod 2) = 0

Dim NumeriResto0 = Numeri.Where(Predicate)

For Each numero In NumeriResto0
    Console.WriteLine(numero)
Next
Avendo una matrice di Integer, l'Estensione .Where non fà altro che passare tutti gli elementi, presenti nella matrice (quindi di tipo Integer), come parametro alla Espressione Lambda del Delegato, e ritornerà un elenco di elementi che avranno soddisfatto una condizione, condizione che verrà eseguita nella sezione "Risultato" della Espressione Lambda.

Ma in questo modo restiamo vincolati alla variabile dichiarata "Predicate" che ci dice chiaramente che possiamo passare solo elementi Integer e Ritornare valore Booleano, quindi nel caso di una matrice di Stringhe, in cui volessimo usare LINQ per effettuare un filtraggio degli elementi, saremmo costretti a creare un altro Predicato che accetti il Tipo String come parametro passato, ed a questo punto verrebbe meno la dinamicità e flessibilità delle query eseguite (questo si intenda come parere personale non fondato)

Possiamo quindi omettere di dichiarare un Predicato preventivamente ed utilizzare direttamente la nostra Espressione Lambda passata come parametro/delegato, quello che determinerà il Tipo di dati passati alla funzione, sarà il Tipo di dati contenuti nella Matrice da filtrare.

Cerco di spiegare meglio con l'esempio precedente
codice:
Dim Numeri As Integer() = {1, 2, 3, 4, 5, 6, 7, 8, 9}

Dim NumeriResto0 = Numeri.Where(Function(num) (num Mod 2) = 0)

For Each numero In NumeriResto0
    Console.WriteLine(numero)
Next
Abbiamo passato direttamente l' Espressione Lambda come delegato e da come si può vedere non abbiamo specificato il Tipo di num perchè verrà dedotto dal Tipo degli elementi contenuti nella Matrice.

Facciamo un altro esempio "dinamico" con una matrice di Stringhe
Dim Animali As String() = {"Cane", "Gatto", "Topo", "Gabbiano", "Leone", "Giaguaro"}
Dim AnimaliFiltro = Animali.Where(Function(animale) animale.StartsWith("G"))
For Each animale In AnimaliFiltro
Console.WriteLine(animale)
Next
Conclusioni:

Scusate se sono stato prolisso ma l'argomento è abbastanza vasto, spero comunque di aver riassunto nel migliore dei modi e di aver portato a vostra conoscenza una delle tante nuove Funzionalità del linguaggio .Net, che non mi fanno rimpiangere il buon vecchio vb6.

Per chi già conoscesse le Lambda Expressions, spero di aver rinfrescato la memoria.

Se qualcuno dovesse trovare delle imprecisioni o inesattezze, sono ben accetti commenti e motivazioni. Cosi da rendere l'articolo più preciso.

Alla prossima.