Casino online











Mercato forex






A41. I Generics, Parte I


Panoramica sui Generics
I Generics costituiscono una delle più importanti aggiunte apportate al Vb.Net rispetto a quello classico. Essi permettono di scrivere codice che opera con tipi differenti di argomenti, definiti, però, solo una vola che il metodo o la classe viene utilizzata: il loro funzionamento potrebbe sembrare simile alla tecnica già vista che utilizza più definizioni di metodi o classi modificate tramite polimorfismo. Per capire meglio l'argomento, facciamo un esempio banale. La collezione ArrayList, molte volte impiegata negli esempi dei precedeti capitoli, permette di immagazzinare qualsiasi tipo di dato, memorizzando, quindi, variabili di tipo Object. Come già detto all'inizio del corso, l'uso di Object comporta molti rischi sia a livello di prestazioni, dovute alle continue operazioni di boxing e unboxing (e le garbage collection che ne conseguono, data la creazione di molti oggetti temporanei), sia a livello di correttezza del codice. Un esempio di questo ultimo caso si verifica quando si tenta di scorrere un ArrayList mediante un ciclo For Each e si incontra un record che non è del tipo specificato, ad esempio:
Dim A As New ArrayList
A.Add(2)
A.Add(3)
A.Add("C") 
'A run-time, sarà lanciata un'eccezione inerente il cast
'poichè la stringa "C" non è del tipo specificato nel blocco For Each
For Each V As Int32 In A
  Console.WriteLine(V)
Next 
Infatti, se l'applicazione dovesse erroneamente inserire una stringa al posto di un numero intero, non verrebbe generato nessun errore, ma si verificherebbe un'eccezione successivamente. Altra problematica legata all'uso di collezioni a tipizzazione debole (ossia che registrano generici oggetti Object, come l'ArrayList, l'HashTable o la SortedList) è dovuta al fatto che sia necessaria una conversione esplicita di tipo nell'uso dei suoi elementi, almeno nella maggioranza dei casi. La soluzione adottata da un programmatore che non conoscesse i generics per risolvere tali inconvenienti sarebbe quella di creare una nuova lista, ex novo, ereditandola da un tipo base come CollectionBase e ridefinendone tutti i metodi (Add, Remove, IndexOf ecc...). L'uso dei Generics, invece, rende molto più veloce e meno insidiosa la scrittura di un codice robusto e solido nell'ambito non solo delle collezioni, ma di molti altri argomenti. Ecco un esempio di come implementare una soluzione basata sui Generics:
'La lista accetta solo oggetti di tipo Int32: per questo motivo
'si genera un'eccezione quando si tenta di inserirvi elementi di
'tipo diverso e la velocità di elaborazione aumenta!
Dim A As New List(Of Int32)
A.Add(1)
A.Add(4)
A.Add(8)
'A.Add("C") '<- Impossibile
For Each V As Int32 In A
  Console.WriteLine(V)
Next 
E questa è una dimostrazione dell'incremento delle prestazioni:
Module Module1
    Sub Main()
        Dim TipDebole As New ArrayList
        Dim TipForte As New List(Of Int32)
        Dim S As New Stopwatch

        'Cronometra le operazioni su ArrayList
        S.Start()
        For I As Int32 = 1 To 1000000
            TipDebole.Add(I)
        Next
        S.Stop()
        Console.WriteLine(S.ElapsedMilliseconds & _ 
            " millisecondi per ArrayList!")

        'Cronometra le operazioni su List
        S.Reset()
        S.Start()
        For I As Int32 = 1 To 1000000
            TipForte.Add(I)
        Next
        S.Stop()
        Console.WriteLine(S.ElapsedMilliseconds & _ 
            " millisecondi per List(Of T)!")

        Console.ReadKey()
    End Sub
End Module 
Sul mio computer portatile l'ArrayList impiega 197ms, mentre List 33ms: i Generics incrementano la velocità di 6 volte!
Oltre a List, esistono anche altre collezioni generic, ossia Dictionary e SortedDictionary: tutti questi sono la versione a tipizzazione forte delle normali collezioni già viste. Ma ora vediamo come scrivere nuove classi e metodi generic.


Generics standard e Generics multpli
La variabili di un certo tipo possono contenere qualsiasi valore di quel dato tipo: esse sono quindi un'astrazione di tutti i possibili valori che si possono assegnare. Un parametro generic, invece, è l'astrazone di tutti i possibili tipi che una classe o un metodo possono prendere in considerazione. La sintassi con cui si dichiara una classe generic è la seguente:
Class [Nome della classe](Of [Nome tipo])

End Class 
Ad esempio:
Class Prova(Of T)

End Class 
T rappresenta un qualsiasi tipo che la classe dovrà elaborare: la definizione di un tipo generic come visto sopra, ossia nella dichiarazione di una classe o di un metodo, viene chiamata definizione di tipo generic o tipo generic aperto.
Dim P As New Prova(Of String) 
Invece, il nome di questa definizione, ossia nella dichiarazione di una variabile o nella sua inizializzazione, viene detta tipo generic collegato. Ecco un semplice esempio di una classe generica:
Module Module1
    'Questa classe è in grado di scrivere su un file
    'molti tipi diversi di dati senza usare conversioni
    Class DataWriter(Of T)
        Private _FileName As String
        Private Writer As IO.StreamWriter
        Private IsOpen As Boolean

        Public ReadOnly Property FileName() As String
            Get
                Return _FileName
            End Get
        End Property

        Sub New(ByVal FileName As String)
            _FileName = FileName
            Writer = New IO.StreamWriter(FileName)
            IsOpen = True
        End Sub

        'Scrive un qualsiasi tipo di dato sul file
        Sub Write(ByVal Value As T)
            If IsOpen Then
                Writer.WriteLine(Value.ToString)
            End If
        End Sub

        Sub Close()
            Writer.Close()
            IsOpen = False
        End Sub
    End Class

    Sub Main()
        'Non bisogna fare confusione con le parentesi: prima va
        'indicato il tipo generic collegato e poi gli eventuali
        'parametri del costruttore
        Dim D1 As New DataWriter(Of String)("C:\t_stringhe.txt")
        Dim D2 As New DataWriter(Of Date)("C:\t_date.txt")

        'Scrive una stringa
        D1.Write("Ciao mondo!")
        'Scrive la data odierna
        D2.Write(Date.Now)

        D1.Close()
        D2.Close()

        Console.ReadKey()
    End Sub
End Module 
Dopo essere stato dichiarato in testa alla classe, il nome arbitrario "T" è usabile come se fosse il nome di un normalissimo tipo, poichè ne rappresenta uno qualsiasi: infatti, nella procedura Writer, il paramtro Value è di tipo T. Da notare è che si possono anche definire più generics in una sola volta, ad esempio una variabile di tipo List di tipo DataWriter di tipo String:
'Variabile contenente una lista di DataWriter di String:
Dim D As New List(Of DataWriter(Of String)) 
Se l'attenzione è focalizzata su un solo metodo anzichè su una classe intera, si dichiara solamente quel metodo come genrics, e la sintassi rimane simile:
[Sub/Function] (Of [Tipo])([Elenco parametri])
    '...
End [Sub/Function] 
Ad esempio, questa procedura scambia due valori qualsiasi, passati per indirizzo:
Module Module1
    Public Sub Swap(Of T)(ByRef Arg1 As T, ByRef Arg2 As T)
        Dim Temp As T = Arg1
        Console.WriteLine("Prima: Arg1 = " & Arg1.ToString & _ 
            "  Arg2 = " & Arg2.ToString)
            
        Arg1 = Arg2
        Arg2 = Temp
        
        Console.WriteLine("Dopo:  Arg1 = " & Arg1.ToString & _
            "  Arg2 = " & Arg2.ToString)
        Console.WriteLine()
    End Sub

    Sub Main()
        Dim X, Y As Double
        Dim Z As Single
        Dim A, B As String

        X = 90.0
        Y = 67.58
        Z = 23.01
        A = "Ciao"
        B = "Mondo"

        'X e Y sono Double, quindi richiama il metodo con T = Double
        Swap(X, Y)
        'A e B sono String, quindi richiama il metodo con T = String
        Swap(A, B)
        
        'Qui viene generato un errore: nonostante Z sia convertibile in
        'Double implicitamente senza perdita di dati, il suo tipo non
        'corrisponde a quello di X, dato che c'è un solo T, che può 
        'assumere un solo valore-tipo. Per questo è necessario 
        'utilizzare una scappatoia
        'Swap(Z, X)
        'Soluzione 1: si esplicita il tipo con un tipo generic collegato
        Swap(Of Double)(Z, X)
        'Soluzione 2: si converte Z in double esplicitamente
        Swap(CDbl(Z), X)

        'Da notare che la conversione esplicita in Double o 
        'l'esplicitazione del tipo generic collegato 

        Console.ReadKey()
    End Sub
End Module 
I generic multipli consentono di usare più tipi indefiniti anzichè uno solo, e la loro sintassi è identica a quelli appena visti: la virgola seprata ogni singola definizione di tipo generic.
Module Module2
    'Una relazione qualsiasi fra due oggetti di tipo indeterminato
    Public Class Relation(Of T1, T2)
        Private Obj1 As T1
        Private Obj2 As T2

        Public ReadOnly Property FirstObject() As T1
            Get
                Return Obj1
            End Get
        End Property

        Public ReadOnly Property SecondObject() As T2
            Get
                Return Obj2
            End Get
        End Property

        Sub New(ByVal Obj1 As T1, ByVal Obj2 As T2)
            Me.Obj1 = Obj1
            Me.Obj2 = Obj2
        End Sub
    End Class

    Sub Main()
        'Crea una relazione fra uno studente e un insegnante,
        'utilizzando le classi create nei capitoli precedenti
        Dim R As Relation(Of Student, Teacher)
        Dim S As New Student("Pinco", "Pallino", Date.Parse("25/06/1990"), _
            "Liceo Scientifico N. Copernico", 4)
        Dim T As New Teacher("Mario", "Rossi", Date.Parse("01/07/1950"), _
        "Matematica")

        'Crea una nuova relazione tra lo studente e l'insegnante
        R = New Relation(Of Student, Teacher)(S, T)
        Console.WriteLine(R.FirstObject.CompleteName)
        Console.WriteLine(R.SecondObject.CompleteName)

        Console.ReadKey()
    End Sub
End Module 
Tutto quello che è stato detto vale anche per le interfacce, in cui si usano le stesse modalità di dichiarazione. Prima di procedere, però, bisogna evidenziare alcun caratteristiche dei generics non menzionate fin'ora:
  • Si può sempre assegnare Nothing a un tipo generic: nel caso sia un tipo reference, il valore sarà normalmente Nothing, altrimenti sarà il valore di default per quel tipo value
  • Non è possibile ereditare un tipo generic, come ad esempio:
    Class Prova(Of T)
        Inherits T
    End Class 
  • Non è neanche possibile implementare un tipo generic
  • Classi o metodi omonimi, ma con signature generic differente, sono considerate/i in overload




 

The Totem's Lair - Copyright (C) 2009
È vietata la riproduzione sia totale che parziale del sito.