Questo sito utilizza cookies, anche di terze parti, per mostrare pubblicità e servizi in linea con il tuo account. Leggi l'informativa sui cookies.
Username: Password: oppure
Guida al Visual Basic .NET - I Delegate Multicast

Guida al Visual Basic .NET

Capitolo 35° - I Delegate Multicast

<< Precedente Prossimo >>


Al contrario di un delegate semplice, un delegate multicast può contenere riferimenti a più metodi insieme, purché della stessa categoria e con la stessa signature. Dato che il costruttore è sempre lo stesso e accetta un solo parametro, non è possibile creare delegate multicast in fase di inizializzazione. L'unico modo per farlo è richiamare il metodo statico Combine della classe System.Delegate (ossia la classe base di tutti i delegate). Combine espone anche un overload che permette di unire molti delegate alla volta, specificandoli tramite un ParamArray. Dato che un delegate multicast contiene più riferimenti a metodi distinti, si parla di invocation list (lista di invocazione) quando ci si riferisce all'insieme di tutti i metodi memorizzati in un delegate multicast. Ecco un semplice esempio:
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 da solo non è valido in questo caso
        D = System.Delegate.Combine(New IsMyFile(AddressOf PrintFile), _
            New IsMyFile(AddressOf CopyFile))
        'Per la cronaca, Combine è un metodo factory

        '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  
La funzione Combine, tuttavia, nasconde molte insidie. Infatti, essendo un metodo factory della classe System.Delegate, come abbiamo detto nel capitolo relativo ai metodi factory, restituisce un oggetto di tipo System.Delegate. Nell'esempio, noi abbiamo potuto assegnare il valore restituito da Combine a D, che è di tipo IsMyFile, perchè solitamente le opzioni di compilazione permettono di eseguire conversioni implicite di questo tipo - ossia Option Strict è solitamente impostato su Off (per ulteriori informazioni, vedere il capitolo sulle opzioni di compilazione). Come abbiamo detto nel capitolo sulle conversioni, assegnare il valore di una classe derivata a una classe base è lecito, poichè nel passaggio da una all'altra non si perde alcun dato, ma si generelizza soltanto il valore rappresentato; eseguire il passaggio inverso, invece, ossia assegnare una classe base a una derivata, può risultare in qualche strano errore perchè i membri in più della classe derivata sono vuoti. Nel caso dei delegate, che sono oggetti immutabili, e che quindi non espongono proprietà modificabili, questo non è un problema, ma il compilatore questo non lo sa. Per essere sicuri, è meglio utilizzare un operatore di cast come DirectCast:
DirectCast(System.Delegate.Combine(A, B), IsMyFile) 
N.B.: Quando un delegate multicast contiene delle funzioni e viene richiamato, il valore restituito è quello della prima funzione memorizzata.
Ecco ora un altro esempio molto articolato sui delegate multicast:
'Questo esempio si basa completamente sulla manipolazione
'di file e cartelle, argomento non ancora affrontato. Se volete,
'potete dare uno sguardo ai capitoli relativi nelle parti
'successive della guida, oppure potete anche limitarvi a leggere
'i commenti, che spiegano tutto ciò che accade.
Module Module1
    'In questo esempio eseguiremo delle operazioni su file con i delegate.
    'Nel menù sarà possibile scegliere quali operazioni
    'eseguire (una o tutte insieme) e sotto quali condizioni modificare
    'un file.
    'Il delegate FileFilter rappresenta una funzione che restituisce
    'True se la condizione è soddisfatta. Le condizioni
    'sono racchiuse in un delegate multicast che contiene più
    'funzioni di questo tipo
    Delegate Function FileFilter(ByVal FileName As String) As Boolean
    'Il prossimo delegate rappresenta un'operazione su un file
    Delegate Sub MassFileOperation(ByVal FileName As String)
    'AskForData è un delegate del tipo più semplice.
    'Servirà per reperire le informazioni necessarie ad
    'eseguire le operazioni (ad esempio, se si sceglie di copiare
    'tutti i file di una cartella, si dovrà anche scegliere
    'dove copiare questi file).
    Delegate Sub AskForData()

    'Queste variabili globali rappresentano le informazioni necesarie
    'per lo svolgimento delle operazioni o la verifica delle condizioni.
    
    'Stringa di formato per rinominare i file
    Dim RenameFormat As String
    'Posizione di un file nella cartella
    Dim FileIndex As Int32
    'Directory in cui copiare i file
    Dim CopyDirectory As String
    'File in cui scrivere. Il tipo StreamWriter permette di scrivere
    'facilmente stringhe su un file usando WriteLine come in Console
    Dim LogFile As IO.StreamWriter
    'Limitazioni sulla data di creazione del file
    Dim CreationDateFrom, CreationDateTo As Date
    'Limitazioni sulla data di ultimo accesso al file
    Dim LastAccessDateFrom, LastAccessDateTo As Date
    'Limitazioni sulla dimensione
    Dim SizeFrom, SizeTo As Int64

    'Rinomina un file
    Sub Rename(ByVal Path As String)
        'Ne prende il nome semplice, senza estensione
        Dim Name As String = IO.Path.GetFileNameWithoutExtension(Path)
        'Apre un oggetto contenente le informazioni sul file
        'di percorso Path
        Dim Info As New IO.FileInfo(Path)

        'Formatta il nome secondo la stringa di formato RenameFormat
        Name = String.Format(RenameFormat, _
                Name, FileIndex, Info.Length, Info.LastAccessTime, Info.CreationTime)
        'E aggiunge ancora l'estensione al nome modificato
        Name &= IO.Path.GetExtension(Path)
        'Copia il vecchio file nella stessa cartella, ma con il nuovo nome
        IO.File.Copy(Path, IO.Path.GetDirectoryName(Path) & "" & Name)
        'Elimina il vecchio file
        IO.File.Delete(Path)
        
        'Aumenta l'indice di uno
        FileIndex += 1
    End Sub

    'Funzione che richiede i dati necessari per far funzionare
    'il metodo Rename
    Sub InputRenameFormat()
        Console.WriteLine("Immettere una stringa di formato valida per rinominare i file.")
        Console.WriteLine("I parametri sono:")
        Console.WriteLine("0 = Nome originale del file;")
        Console.WriteLine("1 = Posizione del file nella cartella, in base 0;")
        Console.WriteLine("2 = Dimensione del file, in bytes;")
        Console.WriteLine("3 = Data dell'ultimo accesso;")
        Console.WriteLine("4 = Data di creazione.")
        RenameFormat = Console.ReadLine
    End Sub

    'Elimina un file di percorso Path
    Sub Delete(ByVal Path As String)
        IO.File.Delete(Path)
    End Sub

    'Copia il file da Path alla nuova cartella
    Sub Copy(ByVal Path As String)
        IO.File.Copy(Path, CopyDirectory & "" & IO.Path.GetFileName(Path))
    End Sub

    'Richiede una cartella valida in cui copiare i file. Se non esiste,
    la crea
    Sub InputCopyDirectory()
        Console.WriteLine("Inserire una cartella valida in cui copiare i file:")
        CopyDirectory = Console.ReadLine
        If Not IO.Directory.Exists(CopyDirectory) Then
            IO.Directory.CreateDirectory(CopyDirectory)
        End If
    End Sub

    'Scrive il nome del file sul file aperto
    Sub Archive(ByVal Path As String)
        LogFile.WriteLine(IO.Path.GetFileName(Path))
    End Sub

    'Chiede il nome di un file su cui scrivere tutte le informazioni
    Sub InputLogFile()
        Console.WriteLine("Inserire il percorso del file su cui scrivere:")
        LogFile = New IO.StreamWriter(Console.ReadLine)
    End Sub

    'Verifica che la data di creazione del file cada tra i limiti fissati
    Function IsCreationDateValid(ByVal Path As String) As Boolean
        Dim Info As New IO.FileInfo(Path)
        Return (Info.CreationTime >= CreationDateFrom) And (Info.CreationTime >= CreationDateTo)
    End Function

    'Richiede di immettere una limitazione temporale per considerare
    'solo certi file
    Sub InputCreationDates()
        Console.WriteLine("Verranno considerati solo i file con data di creazione:")
        Console.Write("Da: ")
        CreationDateFrom = Date.Parse(Console.ReadLine)
        Console.Write("A: ")
        CreationDateTo = Date.Parse(Console.ReadLine)
    End Sub

    'Verifica che la data di ultimo accesso al file cada tra i limiti fissati
    Function IsLastAccessDateValid(ByVal Path As String) As Boolean
        Dim Info As New IO.FileInfo(Path)
        Return (Info.LastAccessTime >= LastAccessDateFrom) And (Info.LastAccessTime >= LastAccessDateTo)
    End Function

    'Richiede di immettere una limitazione temporale per considerare
    'solo certi file
    Sub InputLastAccessDates()
        Console.WriteLine("Verranno considerati solo i file con data di creazione:")
        Console.Write("Da: ")
        LastAccessDateFrom = Date.Parse(Console.ReadLine)
        Console.Write("A: ")
        LastAccessDateTo = Date.Parse(Console.ReadLine)
    End Sub

    'Verifica che la dimensione del file sia coerente coi limiti fissati
    Function IsSizeValid(ByVal Path As String) As Boolean
        Dim Info As New IO.FileInfo(Path)
        Return (Info.Length >= SizeFrom) And (Info.Length >= SizeTo)
    End Function

    'Richiede di specificare dei limiti dimensionali per i file
    Sub InputSizeLimit()
        Console.WriteLine("Verranno considerati solo i file con dimensione compresa:")
        Console.Write("Tra (bytes):")
        SizeFrom = Console.ReadLine
        Console.Write("E (bytes):")
        SizeTo = Console.ReadLine
    End Sub

    'Classe che rappresenta un'operazione eseguibile su file
    Class Operation
        Private _Description As String
        Private _Execute As MassFileOperation
        Private _RequireData As AskForData
        Private _Enabled As Boolean

        'Descrizione
        Public Property Description() As String
            Get
                Return _Description
            End Get
            Set(ByVal value As String)
                _Description = value
            End Set
        End Property

        'Variabile che contiene l'oggetto delegate associato 
        'a questa operazione, ossia un riferimento a una delle Sub
        'definite poco sopra
        Public Property Execute() As MassFileOperation
            Get
                Return _Execute
            End Get
            Set(ByVal value As MassFileOperation)
                _Execute = value
            End Set
        End Property

        'Variabile che contiene l'oggetto delegate che serve
        'per reperire informazioni necessarie ad eseguire
        'l'operazione, ossia un riferimento a una delle sub
        'di Input definite poco sopra. E' Nothing quando
        'non serve nessun dato ausiliario (come nel caso
        'di Delete)
        Public Property RequireData() As AskForData
            Get
                Return _RequireData
            End Get
            Set(ByVal value As AskForData)
                _RequireData = value
            End Set
        End Property

        'Determina se l'operazione va eseguita oppure no
        Public Property Enabled() As Boolean
            Get
                Return _Enabled
            End Get
            Set(ByVal value As Boolean)
                _Enabled = value
            End Set
        End Property

        Sub New(ByVal Description As String, _ 
            ByVal ExecuteMethod As MassFileOperation, _ 
            ByVal RequireDataMethod As AskForData)
            Me.Description = Description
            Me.Execute = ExecuteMethod
            Me.RequireData = RequireDataMethod
            Me.Enabled = False
        End Sub
    End Class

    'Classe che rappresenta una condizione a cui sottoporre
    'i file nella cartella: verranno elaborati solo quelli che
    'soddisfano tutte le condizioni
    Class Condition
        Private _Description As String
        Private _Verify As FileFilter
        Private _RequireData As AskForData
        Private _Enabled As Boolean

        Public Property Description() As String
            Get
                Return _Description
            End Get
            Set(ByVal value As String)
                _Description = value
            End Set
        End Property

        'Contiene un oggetto delegate associato a una delle
        'precedenti funzioni
        Public Property Verify() As FileFilter
            Get
                Return _Verify
            End Get
            Set(ByVal value As FileFilter)
                _Verify = value
            End Set
        End Property

        Public Property RequireData() As AskForData
            Get
                Return _RequireData
            End Get
            Set(ByVal value As AskForData)
                _RequireData = value
            End Set
        End Property

        Public Property Enabled() As Boolean
            Get
                Return _Enabled
            End Get
            Set(ByVal value As Boolean)
                _Enabled = value
            End Set
        End Property

        Sub New(ByVal Description As String, _ 
            ByVal VerifyMethod As FileFilter, _ 
            ByVal RequireDataMethod As AskForData)
            Me.Description = Description
            Me.Verify = VerifyMethod
            Me.RequireData = RequireDataMethod
        End Sub
    End Class

    Sub Main()
        'Contiene tutte le operazioni da eseguire: sarà, quindi, un
        'delegate multicast
        Dim DoOperations As MassFileOperation
        'Contiene tutte le condizioni da verificare
        Dim VerifyConditions As FileFilter
        'Indica la cartella di cui analizzare i file
        Dim Folder As String
        'Hashtable di caratteri-Operation o carattri-Condition. Il
        'carattere indica quale tasto è necessario
        'premere per attivare/disattivare l'operazione/condizione
        Dim Operations As New Hashtable
        Dim Conditions As New Hashtable
        Dim Cmd As Char

        'Aggiunge le operazioni esistenti. La 'c' messa dopo la stringa
        'indica che la costante digitata è un carattere e non una
        'stringa. Il sistema non riesce a distinguere tra stringhe di 
        lunghezza 1 e caratteri, al contrario di come accade in C
        With Operations
            .Add("r"c, New Operation("Rinomina tutti i file nella cartella;", _
                    New MassFileOperation(AddressOf Rename), _
                    New AskForData(AddressOf InputRenameFormat)))
            .Add("c"c, New Operation("Copia tutti i file nella cartella in un'altra cartella;", _
                    New MassFileOperation(AddressOf Copy), _
                    New AskForData(AddressOf InputCopyDirectory)))
            .Add("a"c, New Operation("Scrive il nome di tutti i file nella cartella su un file;", _
                    New MassFileOperation(AddressOf Archive), _
                    New AskForData(AddressOf InputLogFile)))
            .Add("d"c, New Operation("Cancella tutti i file nella cartella;", _
                    New MassFileOperation(AddressOf Delete), _
                    Nothing))
        End With

        'Aggiunge le condizioni esistenti
        With Conditions
            .Add("r"c, New Condition("Seleziona i file da elaborare in base alla data di creazione;", _
                    New FileFilter(AddressOf IsCreationDateValid), _
                    New AskForData(AddressOf InputCreationDates)))
            .Add("l"c, New Condition("Seleziona i file da elaborare in base all'ultimo accesso;", _
                    New FileFilter(AddressOf IsLastAccessDateValid), _
                    New AskForData(AddressOf InputLastAccessDates)))
            .Add("s"c, New Condition("Seleziona i file da elaborare in base alla dimensione;", _
                    New FileFilter(AddressOf IsSizeValid), _
                    New AskForData(AddressOf InputSizeLimit)))
        End With

        Console.WriteLine("Modifica in massa di file ---")
        Console.WriteLine()

        Do
            Console.WriteLine("Immetti il percorso della cartella su cui operare:")
            Folder = Console.ReadLine
        Loop Until IO.Directory.Exists(Folder)

        Do
            Console.Clear()
            Console.WriteLine("Premere la lettera corrispondente per selezionare la voce.")
            Console.WriteLine("Premere 'e' per procedere.")
            Console.WriteLine()
            For Each Key As Char In Operations.Keys
                'Disegna sullo schermo una casella di spunta, piena:
                ' [X] 
                'se l'operazione è attivata, altrimenti vuota:
                ' [ ]
                Console.Write("[")
                If Operations(Key).Enabled = True Then
                    Console.Write("X")
                Else
                    Console.Write(" ")
                End If
                Console.Write("] ")
                'Scrive quindi il carattere da premere e vi associa la descrizione
                Console.Write(Key)
                Console.Write(" - ")
                Console.WriteLine(Operations(Key).Description)
            Next
            Cmd = Console.ReadKey().KeyChar
            If Operations.ContainsKey(Cmd) Then
                Operations(Cmd).Enabled = Not Operations(Cmd).Enabled
            End If
        Loop Until Cmd = "e"c

        Do
            Console.Clear()
            Console.WriteLine("Premere la lettera corrispondente per selezionare la voce.")
            Console.WriteLine("Premere 'e' per procedere.")
            Console.WriteLine()
            For Each Key As Char In Conditions.Keys
                Console.Write("[")
                If Conditions(Key).Enabled = True Then
                    Console.Write("X")
                Else
                    Console.Write(" ")
                End If
                Console.Write("] ")
                Console.Write(Key)
                Console.Write(" - ")
                Console.WriteLine(Conditions(Key).Description)
            Next
            Cmd = Console.ReadKey().KeyChar
            If Conditions.ContainsKey(Cmd) Then
                Conditions(Cmd).Enabled = Not Conditions(Cmd).Enabled
            End If
        Loop Until Cmd = "e"c

        Console.Clear()
        Console.WriteLine("Acquisizione informazioni")
        Console.WriteLine()

        'Cicla su tutte le operazioni presenti nell'Hashtable. 
        For Each Op As Operation In Operations.Values
            'Se l'operazione è attivata...
            If (Op.Enabled) Then
                'Se richiede dati ausiliari, invoca il delegate memorizzato
                'nella proprietà RequireData. Invoke è un metodo
                'di istanza che invoca i metodi contenuti nel delegate.
                'Si può anche scrivere:
                '  Op.RequireData()()
                'Dove la prima coppia di parentesi indica che la proprietà
                'non è indicizzata e la seconda, in questo caso, specifica
                'che il metodo sotteso dal delegate non richiede parametri.
                'È più comprensibile la prima forma
                If Op.RequireData IsNot Nothing Then
                    Op.RequireData.Invoke()
                End If
                'Se DoOperations non contiene ancora nulla, vi inserisce Op.Execute
                If DoOperations Is Nothing Then
                    DoOperations = Op.Execute
                Else
                    'Altrimenti, combina gli oggetti delegate già memorizzati
                    'con il nuovo
                    DoOperations = System.Delegate.Combine(DoOperations, Op.Execute)
                End If
            End If
        Next

        For Each C As Condition In Conditions.Values
            If C.Enabled Then
                If C.RequireData IsNot Nothing Then
                    C.RequireData.Invoke()
                End If
                If VerifyConditions Is Nothing Then
                    VerifyConditions = C.Verify
                Else
                    VerifyConditions = System.Delegate.Combine(VerifyConditions, C.Verify)
                End If
            End If
        Next

        FileIndex = 0
        For Each File As String In IO.Directory.GetFiles(Folder)
            'Ok indica se il file ha passato le condizioni
            Dim Ok As Boolean = True
            'Se ci sono condizioni da applicare, le verifica
            If VerifyConditions IsNot Nothing Then
                'Dato che nel caso di delegate multicast contenenti
                'rifermenti a funzione, il valore restituito è
                'solo quello della prima funzione e a noi interessano
                'tutti i valori restituiti, dobbiamo enumerare
                'ogni singolo oggetto delegate presente nel
                'delegate multicast e invocarlo singolarmente.
                'Ci viene in aiuto il metodo di istanza GetInvocationList,
                'che restituisce un array di delegate singoli.
                For Each C As FileFilter In VerifyConditions.GetInvocationList()
                    'Tutte le condizioni attive devono essere verificate,
                    'quindi bisogna usare un And
                    Ok = Ok And C(File)
                Next
            End If
            'Se le condizioni sono verificate, esegue le operazioni
            If Ok Then
                Try
                    DoOperations(File)
                Catch Ex As Exception
                    Console.WriteLine("Impossibile eseguire l'operazione: " & Ex.Message)
                End Try
            End If
        Next
        'Chiude il file di log se era aperto
        If LogFile IsNot Nothing Then
            LogFile.Close()
        End If

        Console.WriteLine("Operazioni eseguite con successo!")
        Console.ReadKey()
    End Sub

End Module 
Questo esempio molto artificioso è solo un assaggio delle potenzialità dei delegate (noterete che ci sono anche molti conflitti, ad esempio se si seleziona sia copia che elimina, i file potrebbero essere cancellati prima della copia a seconda dell'ordine di invocazione). Vedremo fra poco come utilizzare alcuni delegate piuttosto comuni messi a disposizione dal Framework, e scopriremo nella sezione B che i delegate sono il meccanismo fondamentale alla base di tutto il sistema degli eventi.


Alcuni membri importanti per i delegate multicast

La classe System.Delegate espone alcuni metodi statici pubblici, molti dei quali sono davvero utili quando si tratta di delegate multicast. Eccone una breve lista:
  • Combine(A, B) o Combine(A, B, C, ...) : fonde insieme più delegate per creare un unico delegate multicast invocando il quale vengono invocati tutti i metodi in esso contenuti;
  • GetInvocationList() : funzione d'istanza che restituisce un array di oggetti di tipo System.Delegate, i quali rappresentano i singoli delegate che sono stati memorizzati nell'unica variabile
  • Remove(A, B) : rimuove l'oggetto delegate B dalla invocation list di A (ossia dalla lista di tutti i singoli delegate memorizzati in A). Si suppone che A sia multicast. Se anche B è multicast, solo l'ultimo elemento dell'invocation list di B viene rimosso da quella di A
  • RemoveAll(A, B) : rimuove tutte le occorrenze degli elementi presenti nell'invocation list di B da quella di A. Si suppone che sia A che B siano multicast


<< Precedente Prossimo >>
A proposito dell'autore

Programmatore e analista .NET 2005/2008/2010 (in particolare C# e VB.NET), anche nell'implementazione Mono per Linux. Conoscenze approfondite di Pascal, PHP, XML, HTML 4.01/5, CSS 2.1/3, Javascript (e jQuery). Conoscenze buone di C, LUA, GML, Ruby, XNA, AJAX e Assembly 68000. Competenze basilari di C++, SQL, Hlsl, Java.