A34. Delegate
I delegate sono oggetti che permettono di richiamare metodi di altri oggetti. Potrebbero essere considerati come variabili che, invece
di contenere un valore intero o decimale o stringa, contengono una procedura o una funzione. Se qualcuno dei lettori avesse studiato altri
linguaggi, si ricorderebbe sicuramente dei tipi procedurali del Pascal o dei puntatori a funzione del C e C++: i delegate sono quasi la stessa
cosa. Esistono solo alcune differenze tra questi e i puntatori a funzione: i delegate sono tipi safe (sicuri), poichè sono controllati dal
CLR e non possono mai puntare ad aree di memoria che non contengano un metodo, mentre i puntatori del C sono alquanto pericolosi, in quanto
potrebbero contenere anche un indirizzo errato e causare una carsh del programma. Inoltre, i primi hanno un altro pregio, ossia sono in grado
di invocare non solo metodi statici, ma anche metodi di istanza. Anche se può non apparire lampante, i delegate vengono impiegati molto, anzi
motlissimo, nell'architettura del .Net: basti pensare che tutti gli eventi sono gestiti per mezzo loro, così come le utili funzioni
ForEach e Find della classe System.Array.
Occorre portare una particolare attenzione durante le prime fasi dell'apprendimento dei delegate, poichè la loro sintassi non è immediata e potrebbe venire confusa con quella di un metodo: è importante ricordare, infatti, che essi sono a tutti gli effetti un tipo di dato (derivano da System.Type), per la precisione un tipo reference. Per questo motivo si comportano esattamente come delle comunissime classi. Ultima cosa prima di procedere: sono oggetti immutabili, una volta inizializzati con un riferimento al relativo metodo non possono essere moficati se non distruggendoli o assegnandovi un nuovo oggetto. Ciò detto, la sintassi è questa:
Prima di procedere vorrei evidenziare alcuni particolari: non è possibile includere nella signature di un delegate nè parametri Optional, nè ParamArray, tuttavia è possibile assegnare al delegate metodi che contemplano questo tipo di parametri. Ad esempio, si può referenziare un delegate che ha come signature un array di elementi con un metodo che accetta un ParamArray dello stesso tipo di elementi; nel confronto delle signature, tuttavia, questo tipo di parametri viene saltato. Da ultimo, è possibile referenziare metodi di istanza semplicemente usando il loro nome completo, come si è fatto con Console, ad esempio:
Quando viene richiamato il metodo Combine, il valore restituito non è un delegate con la stessa signature di quelli immessi, ma uno generico: per questo, se Option Strict è attivato (ossia non si possono eseguire conversioni implicite), bisogna convertire manualmente il delegate generico in uno specifico:
Occorre portare una particolare attenzione durante le prime fasi dell'apprendimento dei delegate, poichè la loro sintassi non è immediata e potrebbe venire confusa con quella di un metodo: è importante ricordare, infatti, che essi sono a tutti gli effetti un tipo di dato (derivano da System.Type), per la precisione un tipo reference. Per questo motivo si comportano esattamente come delle comunissime classi. Ultima cosa prima di procedere: sono oggetti immutabili, una volta inizializzati con un riferimento al relativo metodo non possono essere moficati se non distruggendoli o assegnandovi un nuovo oggetto. Ciò detto, la sintassi è questa:
Con questa riga di codice si vede che una delegate non può referenziare un metodo qualsiasi, ma deve riferirsi a un metodo della stessa tipologia (procedura o funzione) e, soprattutto, con la stessa signature, definita dall'elenco di parametri. Ora, questa dichiarazione equivale a impostare il tipo di un oggetto ma non inizializzarlo: per fare ciò biosgna utilizzare il suo costruttore, che accetta l'indirizzo del metodo. È possibile ottenere l'indirizzo in memoria con l'istruzione AddressOf, ad esempio:Delegate [Sub /Function ]([Elenco parametri])
Le successive chiamate ai metodi via via referenziati da D produrranno questo output:Module Module1 'Dichiarazione di un tipo delegate Sub che accetta un parametro 'di tipo stringa 'Essendo la dichiarazione di un tipo, come Structure o Enum, non 'può essere scritto all'interno di un metodo Delegate Sub Display(ByVal MessageAs String ) 'Una procedura dimostrativa Sub Write1(ByVal SAs String ) Console.WriteLine("1: " & S)End Sub 'Un'altra procedura dimostrativa Sub Write2(ByVal SAs String ) Console.WriteLine("2: " & S)End Sub Sub Main()Dim DAs Display D =New Display(AddressOf Console.WriteLine) 'Invoca il metodo referenziato da D: in questo caso 'equivarrebbe a scrivere Console.WriteLine("Ciao") D("Ciao") 'Reinizializza D, assegnandogli l'indirizzo di Write1 D =New Display(AddressOf Write1) 'È come chiamare Write1("Ciao") D("Ciao") 'Modo alternativo per inizializzare un delegate, si omette 'New e si usa solo AddressOf D =AddressOf Write2 D("Ciao") Console.ReadKey()End Sub End Module
Ciao 1: Ciao 2: CiaoPotrebbe sembrare una perdita di tempo per visualizzare tre semplici messaggi. Tuttavia, se si immagina di aver un programma in cui un delegate gestisce ogni messaggio, quando si decida di cambiare lo stile del messaggio, si dovrà cambiare solo il riferimento del delegate e non tutte le chiamate a quella procedura.
Prima di procedere vorrei evidenziare alcuni particolari: non è possibile includere nella signature di un delegate nè parametri Optional, nè ParamArray, tuttavia è possibile assegnare al delegate metodi che contemplano questo tipo di parametri. Ad esempio, si può referenziare un delegate che ha come signature un array di elementi con un metodo che accetta un ParamArray dello stesso tipo di elementi; nel confronto delle signature, tuttavia, questo tipo di parametri viene saltato. Da ultimo, è possibile referenziare metodi di istanza semplicemente usando il loro nome completo, come si è fatto con Console, ad esempio:
[Delegate] =AddressOf [Oggetto].[Metodo]
Uso dei delegate
Due tra le proprietà più utili della classe delegate sono Target e Method. La prima restituisce un riferimento all'oggetto che ha invocato
il metodo: questo è esattamente ciò che succede quando si richiama l'oggetto sender negli eventi a handler multipli (per ulteriori
informazioni sugli eventi, vedere capitoli 45 e 64). La seconda restituisce un oggetto di tipo MethodInfo, appartenente quindi all'ambito
della Reflection (per ulteriori informazioni sulla Reflection, vedere capitolo 43), che permette di osservare gli attributi del metodo
richiamato. L'uso dei delegate è particolarmente significativo nelle funzioni di ricerca: ad esempio, se si volesse cercare un file
implementando un algoritmo ricorsivo (vedi capitolo 20), si dovrebbe stilare la lista di ogni files nella cartella da anlizzare, mentre
usando un delegate come parametro della procedura si potrebbe fermare la ricerca al file voluto, oppure eseguire altre operazioni su di esso.
Un esempio di questo procedimento:
Io ho immesso "readme.txt" come nome del file e "C:\Programmi" come cartella: troverete sicuramente un file in poco tempo usando questi parametri. Nel sorgente si vede che si usano pochissime righe per far compiere due operazioni molto differenti alla stessa procedura. In altre condizioni, un aspirante programmatore che non conoscesse i delegate avrebbe scritto due procedure intere, sprecando più spazio.Module Module2 'Nome del file da cercare Dim FileAs String 'Questo delegate referenzia una funzione che accetta un parametro 'stringa e restituisce un valore booleano Delegate Function IsMyFile(ByVal FileNameAs String )As Boolean 'Funzione 1, stampa il contenuto del file a schermo Function PrintFile(ByVal FileNameAs String )As Boolean 'Io.Path.GetFileName(F) restituisce solo il nome del 'singolo file F, togliendo il percorso delle cartelle If IO.Path.GetFileName(FileName) = FileThen 'IO.File.ReadAllText(F) restituisce il testo contenuto 'nel file F in una sola operazione Console.WriteLine(IO.File.ReadAllText(FileName))Return TrueEnd If Return FalseEnd Function 'Funzione 2, copia il file sul desktop Function CopyFile(ByVal FileNameAs String )As Boolean If IO.Path.GetFileName(FileName) = FileThen 'IO.File.Copy(S, D) copia il file S nel file D: 'se D non esiste viene creato, se esiste viene 'sovrascritto IO.File.Copy(FileName, _My .Computer.FileSystem.SpecialDirectories.Desktop & _ "\" & File)Return TrueEnd If Return FalseEnd Function 'Procedura ricorsiva che cerca il file Function SearchFile(ByVal DirAs String ,ByVal IsOKAs IsMyFile) _As Boolean 'Ottiene tutte le sottodirectory Dim Dirs()As String = IO.Directory.GetDirectories(Dir) 'Ottiene tutti i files Dim Files()As String = IO.Directory.GetFiles(Dir) 'Analizza ogni file per vedere se è quello cercato For Each FAs String In Files 'È il file cercato, basta cercare If IsOK(F)Then 'Termina la funzione e restituisce Vero, cosicchè 'anche nel for sulle cartelle si termini 'la ricerca Return TrueEnd If Next 'Analizza tutte le sottocartelle For Each DAs String In DirsIf SearchFile(D, IsOK)Then 'Termina ricorsivamente la ricerca Return TrueEnd If Next End Function Sub Main()Dim DirAs String Console.WriteLine("Inserire il nome file da cercare:") File = Console.ReadLine Console.WriteLine("Inserire la cartella in cui cercare:") Dir = Console.ReadLine 'Cerca il file e lo scrive a schermo SearchFile(Dir,AddressOf PrintFile) 'Cerca il file e lo copia sul desktop SearchFile(Dir,AddressOf CopyFile) Console.ReadKey()End Sub End Module
Delegate Multicast
Un delegate multicast può contenere più riferimenti a metodi anzichè uno solo. È possibile unire più delegate in un unico delegate
multicast con il metodo Combine, appartenente alla classe Delegate. Dato che quest'ultima è anche una parola riservata del Vb.Net, per
indicarla, occorre racchiuderla fra parentesi quadre oppure specificarne il nome completo:
Questo codice nasconde una piccolo dettaglio che potrebbe essere sfuggito: se un delegate multicast è formato da due o più funzioni, qual è il valore restituito? Sempre quello della prima funzione, ma in questo caso non c'è problema, poichè il corpo delle funzioni specificate è uguale per quanto riguarda il valore restituito.Module Module2 'Vedi esempio precedente Sub Main()Dim DirAs String Dim DAs IsMyFile Console.WriteLine("Inserire il nome file da cercare:") File = Console.ReadLine Console.WriteLine("Inserire la cartella in cui cercare:") Dir = Console.ReadLine 'Crea un delegate multicast, unendo PrintFile e CopyFile 'Da notare è che in questa espressione è necessario usare 'delle vere e proprie variabili delegate, poichè 'l'operatore AddressOf non è valido in questo caso D = [Delegate].Combine(New IsMyFile(AddressOf PrintFile), _New IsMyFile(AddressOf CopyFile)) 'Ora il file trovato viene sia visualizzato che copiato 'sul desktop SearchFile(Dir, D) 'Se si vuole rimuovere uno o più riferimenti a metodi del 'delegate multicast si deve utilizzare il metodo statico Remove: D = System.Delegate.Remove(D,New IsMyFile(AddressOf CopyFile)) 'Ora D farà visualizzare solamente il file trovato Console.ReadKey()End Sub End Module
Quando viene richiamato il metodo Combine, il valore restituito non è un delegate con la stessa signature di quelli immessi, ma uno generico: per questo, se Option Strict è attivato (ossia non si possono eseguire conversioni implicite), bisogna convertire manualmente il delegate generico in uno specifico:
D = DirectCast([Delegate].Combine(A, B), IsMyFile)Altri due metodi della classe System.Delegate sono RemoveAll e GetInvocationList: quest'ultimo restituisce tanti oggetti MethodInfo quanti sono i metodi nella delegate e permette quindi di richiamarli singolarmente.
The Totem's Lair - Copyright (C) 2009
È vietata la riproduzione sia totale che parziale del sito.



