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:
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:Dim AAs 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 VAs Int32In A Console.WriteLine(V)Next
'E questa è una dimostrazione dell'incremento delle prestazioni: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 AAs New List(Of Int32) A.Add(1) A.Add(4) A.Add(8) 'A.Add("C") '<- Impossibile For Each VAs Int32In A Console.WriteLine(V)Next
Sul mio computer portatile l'ArrayList impiega 197ms, mentre List 33ms: i Generics incrementano la velocità di 6 volte!Module Module1Sub Main()Dim TipDeboleAs New ArrayListDim TipForteAs New List(Of Int32)Dim SAs New Stopwatch 'Cronometra le operazioni su ArrayList S.Start()For IAs Int32 = 1To 1000000 TipDebole.Add(I)Next S.Stop() Console.WriteLine(S.ElapsedMilliseconds & _ " millisecondi per ArrayList!") 'Cronometra le operazioni su List S.Reset() S.Start()For IAs Int32 = 1To 1000000 TipForte.Add(I)Next S.Stop() Console.WriteLine(S.ElapsedMilliseconds & _ " millisecondi per List(Of T)!") Console.ReadKey()End Sub End Module
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:
Ad esempio:Class [Nome della classe](Of [Nome tipo])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.Class Prova(Of T)End Class
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:Dim PAs New Prova(Of String )
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:Module Module1 'Questa classe è in grado di scrivere su un file 'molti tipi diversi di dati senza usare conversioni Class DataWriter(Of T)Private _FileNameAs String Private WriterAs IO.StreamWriterPrivate IsOpenAs Boolean Public ReadOnly Property FileName()As String Get Return _FileNameEnd Get End Property Sub New (ByVal FileNameAs String ) _FileName = FileName Writer =New IO.StreamWriter(FileName) IsOpen = TrueEnd Sub 'Scrive un qualsiasi tipo di dato sul file Sub Write(ByVal ValueAs T)If IsOpenThen Writer.WriteLine(Value.ToString)End If End Sub Sub Close() Writer.Close() IsOpen = FalseEnd 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 D1As New DataWriter(Of String )("C:\t_stringhe.txt")Dim D2As 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
'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:Variabile contenente una lista di DataWriter di String: Dim DAs New List(Of DataWriter(Of String ))
[Sub/Function] (Ad esempio, questa procedura scambia due valori qualsiasi, passati per indirizzo:Of [Tipo])([Elenco parametri]) '... End [Sub/Function]
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 Module1Public Sub Swap(Of T)(ByRef Arg1As T,ByRef Arg2As T)Dim TempAs 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, YAs Double Dim ZAs Single Dim A, BAs 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
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:Module Module2 'Una relazione qualsiasi fra due oggetti di tipo indeterminato Public Class Relation(Of T1, T2)Private Obj1As T1Private Obj2As T2Public ReadOnly Property FirstObject()As T1Get Return Obj1End Get End Property Public ReadOnly Property SecondObject()As T2Get Return Obj2End Get End Property Sub New (ByVal Obj1As T1,ByVal Obj2As T2)Me .Obj1 = Obj1Me .Obj2 = Obj2End Sub End Class Sub Main() 'Crea una relazione fra uno studente e un insegnante, 'utilizzando le classi create nei capitoli precedenti Dim RAs Relation(Of Student, Teacher)Dim SAs New Student("Pinco", "Pallino",Date .Parse("25/06/1990"), _ "Liceo Scientifico N. Copernico", 4)Dim TAs 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
- 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 TEnd 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.



