Dagli Anonymous Delegate alle Lambda Expression di C#3.0

Un mio post precedente sull'uso dei delegate ha generato un lungo follow-up con sviluppatori interessati ad approfondire l'argomento; in particolare, numerose domande riguardavano l'utilizzo dei delegate in funzioni di callback, in chiamate asincrone, nello scorrimento di liste generiche e, naturalmente, nell'integrazione dei delegate in LinQ. Approfitto di questa nota anche per ricordare che è preferibile e più efficiente postare le vostre domande in fondo ai miei blog, invece di scrivermi per mail, così da condividere le nostre interazioni con il resto della community ;-).

L'utilizzo dei delegati negli scenari descritti sopra prevede l'approfondimento di un tipo particolare di delegate, ovvero gli Anonymous Delegate. A questo scopo ho preparato il seguente listato che mostra l'implementazione degli anonymous delegate in diverse situazioni; non cercate di leggerlo subito perché è commentato nel resto del post, ma magari stampate l'immagine linkata che è ad alta definizione per prendere appunti durante la lettura del resto di questo post:image

Come dicevo, ho riassunto tutto il codice insieme per poter meglio confrontare i dettagli che ora vado a spiegare, relativi al pezzettino di volta in volta evidenziato.

Iniziamo ricapitolando il normale uso dei delegati NON anonimi; in pratica, definita una funzione (somma nel mio esempio), la richiamo attraverso un oggetto delegate -che ho chiamato CalcoloDelegatoEsplicito) di CalcoloDelegato che è una classe "tipo puntatore a funzione" (buona definizione di delegate) ad una generica funzione che riceve due interi (a e b) e ne restituisce un terzo; si noti che è stata necessaria una New per instanziare CalcoloDelegatoEsplicito, a sostegno del fatto che i delegate non sono semplici alias bensì delle classi del .NET framework:

image

 

Ora facciamo la stessa cosa utilizzando però un Anonymous Delegate, ovvero rimuovendo la funzione somma e spostandone l'implementazione inline. Si noti che in questo caso non è necessario usare la New per creare l'istanza del delegate; infatti, il compilatore C# è in grado di capire da solo che deve effettuare le seguenti operazioni:

  1. Inferire i valori di ritorno della funzione anonima (ovvero capire che restituisce un intero)
  2. Istanziare un nuovo oggetto delegate del tipo dedotto al punto precedente
  3. Wrappare il nuovo delegate intorno al metodo anonimo
  4. Assegnare quest'ultimo allo specifico delegate CalcoloDelegato

Le uniche istruzioni necessarie a ottenere il risultato sono dunque quelle di seguito evidenziate:

image

 

Ora facciamo un salto di astrazione: usiamo la funzione InvocaDelegateSpecifico che accetta come parametro uno SPECIFICO  delegate (in questo caso si aspetta un delegate della classe CalcolaDelegato, che noi le passiamo dopo averlo creato inline come anonymous delegate):

image

 

Vediamo l'ultimo caso: funzioni che prendono un delegate astratto, come la InvocaDelegateAstratto(Delegate calcolodelegatoastratto); in questo caso dovete prima effettuare il cast ad uno specifico delegate:

image

 

Infine, il caso più interessante: un esempio utile e concreto in cui il costruttore della classe Thread (ma potrebbe essere il metodo Find delle collection) richiede il nome di una funzione senza parametri da lanciare in un thread separato; in questo caso noi gli passiamo un delegate anonimo costruendogli inline le istruzioni da eseguire, che lui riceve come delegate astratto:

image

Un'ultima cosa prima di concludere: nel paragrafo precedente menzionavo che le collection hanno un metodo Find che, in un'unica istruzione, consente di estrarre l'elemento desiderato semplicemente passando a questa funzione la condizione di ricerca (che fa anche uscire dal ciclo); immaginate per esempio di avere una classe Cliente (con un paio di metodi pubblici come nome e città) e una collezione di clienti (che chiamate Clienti) definita con l'istruzione List<Cilente> Clienti = new List<Cliente>(); Ora, per trovare il primo cliente della lista la cui città è Milano, possiamo scrivere la seguente singola istruzione:

  • Cliente cliente = Clienti.Find(delegate (Cliente c) {return c.Città="Milano";});

Ebbene, la parte che ho sottolineato è proprio un delegate anonimo che prende in ingresso un parametro di classe Cliente che viene passato alla funzione Find che si aspetta un delegate astratto contenente l'istruzione di ricerca (quella fra parenti graffe).

Ma c'è di più: il C# 3.0 che è parte di Visual Studio 2008 consente di ridurre ulteriormente l'istruzione sopra sfruttando le cosiddette LAMBDA EXPRESSION: infatti potete ridurre il codice alla seguente istruzione, che significa esattamente la stessa cosa:

  • Cliente cliente = Clienti.Find(c => c.Città="Milano");

 

Mi rendo conto che l'argomento è un po' ostico, e probabilmente se non venite da esperienza di programmazione funzionale vi troverete un po' perplessi a valutare l'utilità di comprimere il codice così tanto. Se vi può consolare, vi confermo che l'alternativa c'è, ovvero scirversi un bel ciclo ForEach che si spazzoli tutti gli elementi della collezione; a quel punto però perdereste il vantaggio di poter passare come parametro la funzione che contiene la condizione di ricerca, che dovrebbe essere sostituita per esempio da una stringa (di cui dovreste fare i parsing) oppure da una serie di parametri per valutare condizioni quali i record con le città che iniziano per una certa lettera, o quelli con il nome utente composto da due parole, o quelli con il nome utente uguale al nome della città... ovviamente questi sono esempi di pura teoria visto che ho creato per semplicità una tabella con due soli campi stringa, ma vi possono dare un'idea del vantaggio di utilizzare funzioni generiche sfruttando gli anonymous delegate.

 

Se vi interessa approfondire l'argomento, io ho trovato ispirazione in questo articolo.

Fatemi sapere se il post vi è piaciuto. Cordialità