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 Generics Parte II

Guida al Visual Basic .NET

Capitolo 42° - I Generics Parte II

<< Precedente Prossimo >>


Interfacce Generics

Proviamo ora a scrivere qualche interfaccia generics per vederne il comportamento. Riprendiamo l'interfaccia IComparer, che indica qualcosa con il compito di comparare oggetti: esiste anche la sua corrispettiva generics, ossia IComparer(Of T). Non fa nessun differenza il comportamento di quest'ultima: l'unica cosa che cambia è il tipo degli oggetti da comparare.
Module Module1
    'Questa classe implementa un comaparatore di oggetti Student
    'in base al loro anno di corso
    Class StudentByGradeComparer
        Implements IComparer(Of Student)

        'Come potete osservare, in questo metodo non viene eseguito
        'nessun tipo di cast, poiché l'interfaccia IComparer(Of T)
        'prevede un metodo Compare a tipizzazione forte. Dato che 
        'abbiamo specificato come tipo generic collegato Student,
        'anche il tipo a cui IComparer si riferisce sarà
        'Student. Possiamo accedere alle proprietà di x e y
        'senza nessun late binding (per ulteriori informazioni, 
        'vedere i capitoli sulla reflection)
        Public Function Compare(ByVal x As Student, ByVal y As Student) As Integer Implements IComparer(Of Student).Compare
            Return x.Grade.CompareTo(y.Grade)
        End Function
    End Class

    Sub Main()
        'Crea un nuovo array di oggeti Student
        Dim S(2) As Student

        'Inizializza ogni oggetto
        S(0) = New Student("Mario", "Rossi", New Date(1993, 2, 3), "Liceo Classico Ugo Foscolo", 2)
        S(1) = New Student("Luigi", "Bianchi", New Date(1991, 6, 27), "Liceo Scientifico Fermi", 4)
        S(2) = New Student("Carlo", "Verdi", New Date(1992, 5, 12), "ITIS Cardano", 1)

        'Ordina l'array con il comparer specificato
        Array.Sort(S, New StudentByGradeComparer())

        'Stampa il profilo di ogni studente: vedrete che essi sono
        'in effetti ordinati in base all'anno di corso
        For Each St As Student In S
            Console.WriteLine(St.Profile)
        Next
        Console.ReadKey()
    End Sub

End Module


I Vincoli

I tipi generics sono molto utili, ma spesso sono un po' troppo... "generici" XD Faccio un esempio. Ammettiamo di avere un metodo generics (Of T) che accetta due parametri A e B. Proviamo a scrivere:
If A = B Then '...
L'IDE ci comunica subito un errore: "Operator '=' is not definited for type 'T' and 'T'." In effetti, poiché T può essere un qualsiasi tipo, non possiamo neanche sapere se questo tipo implementi l'operatore uguale =. In questo caso, vogliamo imporre come condizione, ossia come vincolo, che, per usare il metodo in questione, il tipo generic collegato debba obbligatoriamente esporre un modo per sapere se due oggetti di quel tipo sono uguali. Come si rende in codice? Se fate mente locale sulle interfacce, ricorderete che una classe rappresenta un concetto con determinate caratteristiche se implementa determinate interfacce. Dovremo, quindi, trovare un'interfaccia che rappresenta l'"eguagliabilità": l'interfaccia in questione è IEquatable(Of T). Per poter sapere se due oggetti T sono uguali, quindi, T dovrà essere un qualsiasi tipo che implementa IEquatable(Of T). Ecco che dobbiamo imporre un vincolo al tipo.
Esistono cinque categorie di vincoli:
  • Vincolo di interfaccia;
  • Vincolo di ereditarietà;
  • Vincolo di classe;
  • Vincolo di struttura;
  • Vincolo New.
Iniziamo con l'analizzare il primo di cui abbiamo parlato.


Vincolo di Interfaccia

Il vincolo di interfaccia è indubbiamente uno dei più utili e usati accanto a quello di ereditarietà. Esso impone che il tipo generic collegato implementi l'interfaccia specificata. Dato che dopo l'imposizione del vincolo sappiamo per ipotesi che il tipo T esporrà sicuramente tutti i membri di quell'interfaccia, possiamo richiamare tali membri da tutte le variabili di tipo T. La sintassi è molto semplice:
(Of T As [Interfaccia])
Ecco un esempio:
Module Module1

    'Questa classe rappresenta una collezione di
    'elementi che possono essere comparati. Per questo
    'motivo, il tipo T espone un vincolo di interfaccia
    'che obbliga tutti i tipi generics collegati ad
    'implementare tale interfaccia.
    'Notate bene che in questo caso particolare ho usato
    'un generics doppio, poiché il vincolo non
    'si riferisce a IComparable, ma a IComparable(Of T).
    'D'altra parte, è abbastanza ovvio che se 
    'una collezione contiene un solo tipo di dato,
    'basterà che la comparazione sia possibile
    'solo attraverso oggetti di quel tipo
    Class ComparableCollection(Of T As IComparable(Of T))
        'Ereditiamo direttamente da List(Of T), acquisendone
        'automaticamente tutti i membri base e le caratteristiche.
        'In questo modo, godremo di due grandi vantaggi:
        ' - non dovremo definire tutti i metodi per aggiungere,
        '   rimuovere o cercare elementi, in quanto vengono tutti
        '   ereditati dalla classe base List;
        ' - non dovremo neanche implementare l'interfaccia
        '   IEnumerable(Of T), poiché la classe base la
        '   implementa di per sé.
        Inherits List(Of T)

        'Dato che gli oggetti contenuti in oggetti di
        'questo tipo sono per certo comparabili, possiamo
        'trovarne il massimo ed il minimo.
        
        'Trova il massimo elemento
        Public ReadOnly Property Max() As T
            Get
                If Me.Count > 0 Then
                    Dim Result As T = Me(0)

                    For Each Element As T In Me
                        'Ricordate che A.CompareTo(B) restituisce
                        '1 se A > B
                        If Element.CompareTo(Result) = 1 Then
                            Result = Element
                        End If
                    Next

                    Return Result
                Else
                    Return Nothing
                End If
            End Get
        End Property

        'Trova il minimo elemento
        Public ReadOnly Property Min() As T
            Get
                If Me.Count > 0 Then
                    Dim Result As T = Me(0)

                    For Each Element As T In Me
                        If Element.CompareTo(Result) = -1 Then
                            Result = Element
                        End If
                    Next

                    Return Result
                Else
                    Return Nothing
                End If
            End Get
        End Property

        'Trova tutti gli elementi uguali ad A e ne restituisce
        'gli indici
        Public Function FindEquals(ByVal A As T) As Int32()
            Dim Result As New List(Of Int32)

            For I As Int32 = 0 To Me.Count - 1
                If Me(I).CompareTo(A) = 0 Then
                    Result.Add(I)
                End If
            Next

            'Converte la lista di interi in un array di interi
            'con gli stessi elementi
            Return Result.ToArray()
        End Function

    End Class
    
    Sub Main()
        'Tre collezioni, una di interi, una di stringhe e
        'una di date
        Dim A As New ComparableCollection(Of Int32)
        Dim B As New ComparableCollection(Of String)
        Dim C As New ComparableCollection(Of Date)

        A.AddRange(New Int32() {4, 19, 6, 90, 57, 46, 4, 56, 4})
        B.AddRange(New String() {"acca", "casa", "zen", "rullo", "casa"})
        C.AddRange(New Date() {New Date(2008, 1, 1), New Date(1999, 12, 31), New Date(2100, 4, 12)})

        Console.WriteLine(A.Min())
        ' > 4
        Console.WriteLine(A.Max())
        ' > 90
        Console.WriteLine(B.Min())
        ' > acca
        Console.WriteLine(B.Max())
        ' > zen
        Console.WriteLine(C.Min().ToShortDateString)
        ' > 31/12/1999
        Console.WriteLine(C.Max().ToShortDateString)
        ' > 12/4/2100

        'Trova la posizione degli elementi uguali a 4
        Dim AEqs() As Int32 = A.FindEquals(4)
        ' > 0 6 8
        Dim BEqs() As Int32 = B.FindEquals("casa")
        ' > 1 4

        Console.ReadKey()
    End Sub

End Module


Vincolo di ereditarietà

Ha la stessa sintassi del vincolo di interfaccia, con la sola differenza che al posto dell'interfaccia si specifica la classe dalla quale il tipo generics collegato deve ereditare. I vantaggi sono praticamente uguali a quelli offerti dal vincolo di interfaccia: possiamo trattare T come se fosse un oggetto di tipo [Classe] (una classe qualsiasi) ed utilizzarne i membri, poiché tutti i tipi possibili per T sicuramente derivano da [Classe]. Un esempio anche per questo vincolo mi sembra abbastanza ridondante, ma c'è una caso particolare che mi piacerebbe sottolineare. Mi riferisco al caso in cui al posto della classe base viene specificato un altro tipo generic (aperto), e di questo, data la non immediatezza di comprensione, posso dare un veloce esempio:
Class IsARelation(Of T, U As T)
    Public Base As T
    Public Derived As U
End Class
Questa classe rappresenta una relazione is-a ("è un"), quella famosa relazione che avevo introdotto come esempio una quarantina di capitoli fa durante i primi paragrafi di spiegazione. Questa relazione è rappresentata particolarmente bene, dicevo, se si prende una classe base e la sua classe derivata. I tipi generics aperti non fanno altro che astrarre questo concetto: T è un tipo qualsiasi e U un qualsiasi altro tipo derivato da T o uguale T (non c'è un modo per imporre che sia solo derivato e non lo stesso tipo). Ad esempio, potrebbe essere valido un oggetto del genere:
Dim P As Person
Dim S As Student
'...
Dim A As New IsARelation(Of Person, Student)(P, S)



Vincoli di classe e struttura

Il vincolo di classe impone che il tipo generics collegato sia un tipo reference, mentre il vincolo di struttura impone che sia un tipo value. Le sintassi sono le seguenti:
(Of T As Class)
(Of T As Structure)
Questi due vincoli non sono molto usati, a dire il vero, e la loro utilità non è così marcata e lampante come appare per i primi due vincoli analizzati. Certo, possiamo evitare alcuni comportamenti strani dovuti ai tipi reference, o sfruttare alcune caratteristiche dei tipi value, ma nulla di più. Ecco un esempio dei possibili vantaggi:
  • Vincolo di classe:
    • Possiamo assegnare Nothing con la sicurezza di distruggere l'oggetto e non di cambiarne semplicemente il valore in 0 (o in quello di default per un tipo non numerico);
    • Possiamo usare con sicurezza gli operatori Is, IsNot, TypeOf e DirectCast che funzionano solo con i tipi reference;
  • Vincolo di struttura:
    • Possiamo usare l'operatore = per comparare due valori sulla base di quello che contengono e non di quello che "sono";
    • Possiamo evitare gli inconvenienti dell'assegnamento dovuti ai tipi reference.
Userò il vincolo di classe in un esempio molto significativo, ma solo quando introdurrò la Reflection, quindi fatevi un asterisco su questo capitolo.


Vincolo New

Questo vincolo impone al tipo generic collegato di esporre almeno un costruttore senza parametri. Particolarmente utile quando si devono inizializzare dei valori generics:
Module Module1

    'Con molta fantasia, il vincolo New si dichiara postponendo
    '"As New" al tipo generic aperto.
    Function CreateArray(Of T As New)(ByVal Count As Int32) As T()
        Dim Result(Count - 1) As T

        For I As Int32 = 0 To Count - 1
            'Possiamo usare il costruttore perchè il
            'vincolo ce lo assicura
            Result(I) = New T()
        Next

        Return Result
    End Function

    Sub Main()
        'Crea 10 flussi di dati in memoria. Non abbiamo
        'mai usato questa classe perchè rientra in
        'un argomento che tratterò più avanti, ma
        'è una classe particolarmente utile e versatile
        'che trova applicazioni in molte situazioni.
        'Avere un bel metodo generics che ne crea 10 in una
        'volta è una gran comodità.
        'Ovviamente possiamo fare la stessa cosa con tutti
        'i tipi che espongono almeno un New senza parametri
        Dim Streams As IO.MemoryStream() = CreateArray(Of IO.MemoryStream)(10)

        '...
    End Sub
  
End Module


Vincoli multipli

Un tipo generic aperto può essere sottoposto a più di un vincolo, ossia ad un vincolo multiplo, che altro non è se non la combinazione di due o più vincoli semplici di quelli appena visti. La sintassi di un vincolo multiplo è leggermente diversa e prevede che tutti i vincoli siano raggruppati in una copia di parentesi graffe e separati da virgole:
(Of T As {Vincolo1, Vincolo2, ...})
Ecco un esempio:
Module Module1

    'Classe che filtra dati di qualsiasi natura
    Class DataFilter(Of T)
        Delegate Function FilterData(ByVal Data As T) As Boolean

        'La signature chilometrica è fatta apposta per
        'farvi impazzire XD Vediamo le parti una per una:
        ' - TSerach: deve essere un tipo uguale a T o derivato
        '   da T, in quanto stiamo elaborando elementi di tipo T;
        '   inoltre deve anche essere clonabile, poiché
        '   salveremo solo una copia dei valor trovati.
        '   Questo implica che TSearch sia un tipo reference, e che
        '   quindi lo sia anche T: questa complicazione è solo
        '   per mostrare dei vincoli multipli e potete anche
        '   rimuoverla se vi pare;
        ' - TList: deve essere un tipo reference, esporre un
        '   costruttore senza parametri ed implementare
        '   l'interfaccia IList(Of TSearch), ossia deve
        '   essere una lista;
        ' - ResultList: lista in cui riporre i risultati (passata
        '   per indirizzo);
        ' - Filter: delegate che punta alla funzione usata per
        '   selezionare i valori;
        ' - Data: paramarray contenente i valori da filtrare.
        Sub Filter(Of TSearch As {ICloneable, T}, TList As {IList(Of TSearch), New, Class}) _
            (ByRef ResultList As TList, ByVal Filter As FilterData, ByVal ParamArray Data() As TSearch)
            
            'Se la lista è Nothing, la inizializza.
            'Notare che non avremmo potuto compararla a Nothing
            'senza il vincolo Class, né inizializzarla
            'senza il vincolo New
            If ResultList Is Nothing Then
                ResultList = New TList()
            End If

            'Itera sugli elementi di data
            For Each Element As TSearch In Data
                'E aggiunge una copia di quelli che
                'soddisfano la condizione
                If Filter.Invoke(Element) Then
                    'Aggiunge una copia dell'elemento alla lista.
                    'Anche in questo non avremmo potuto richiamare
                    'Add senza il vincolo interfaccia su IList, né
                    'clonare Element senza il vincolo interfaccia ICloneable
                    ResultList.Add(Element.Clone())
                End If
            Next
        End Sub
    End Class

    'Controlla se la stringa A è palindroma
    Function IsPalindrome(ByVal A As String) As Boolean
        Dim Result As Boolean = True

        For I As Int32 = 0 To (A.Length / 2) - 1
            If A.Chars(I) <> A.Chars(A.Length - 1 - I) Then
                Result = False
                Exit For
            End If
        Next

        Return Result
    End Function

    Sub Main()
        Dim DF As New DataFilter(Of String)
        'Lista di stringhe: notare che la variabile non
        'contiene nessun oggetto perchè non abbiamo usato New.
        'Serve per mostrare che verrà inizializzata
        'da DF.Filter.
        Dim L As List(Of String)

        'Analizza le stringhe passate, trova quelle palindrome
        'e le pone in L
        DF.Filter(L, AddressOf IsPalindrome, _ 
            "casa", "pane", "anna", "banana", "tenet", "radar")

        For Each R As String In L
            Console.WriteLine(R)
        Next

        Console.ReadKey()
    End Sub

End Module


<< 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.