Casino online











Mercato forex






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:
Delegate [Sub/Function]([Elenco parametri]) 
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:
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 Message As String)

    'Una procedura dimostrativa
    Sub Write1(ByVal S As String)
        Console.WriteLine("1: " & S)
    End Sub

    'Un'altra procedura dimostrativa
    Sub Write2(ByVal S As String)
        Console.WriteLine("2: " & S)
    End Sub

    Sub Main()
        Dim D As 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 
Le successive chiamate ai metodi via via referenziati da D produrranno questo output:
Ciao
1: Ciao
2: Ciao 
Potrebbe 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:
Module Module2
    'Nome del file da cercare
    Dim File As String

    'Questo delegate referenzia una funzione che accetta un parametro 
    'stringa e restituisce un valore booleano
    Delegate Function IsMyFile(ByVal FileName As String) As Boolean

    'Funzione 1, stampa il contenuto del file a schermo
    Function PrintFile(ByVal FileName As 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) = File Then
            'IO.File.ReadAllText(F) restituisce il testo contenuto
            'nel file F in una sola operazione
            Console.WriteLine(IO.File.ReadAllText(FileName))
            Return True
        End If
        Return False
    End Function

    'Funzione 2, copia il file sul desktop
    Function CopyFile(ByVal FileName As String) As Boolean
        If IO.Path.GetFileName(FileName) = File Then
            '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 True
        End If
        Return False
    End Function

    'Procedura ricorsiva che cerca il file
    Function SearchFile(ByVal Dir As String, ByVal IsOK As 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 F As 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 True
            End If
        Next

        'Analizza tutte le sottocartelle
        For Each D As String In Dirs
            If SearchFile(D, IsOK) Then
                'Termina ricorsivamente la ricerca
                Return True
            End If
        Next
    End Function

    Sub Main()
        Dim Dir As 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 
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.


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:
Module Module2
    'Vedi esempio precedente
    Sub Main()
        Dim Dir As String
        Dim D As 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 
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.
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.