Guida al Visual Basic .NET
Capitolo 41° - I Generics Parte I
Panoramica sui GenericsI Generics sono un concetto molto importante per quanto riguarda la programmazione ad oggetti, specialmente in .NET e, se fino ad ora non ne conoscevate nemmeno l'esistenza, d'ora in poi non potrete farne a meno. Cominciamo col fare un paragone per esemplificare il concetto di generics. Ammettiamo di dichiarare una variabile I di tipo Int32: in questa variabile potremo immagazzinare qualsiasi informazione che consista di un numero intero rappresentabile su 32 bit. Possiamo dire, quindi, che il tipo Int32 costituisce un'astrazione di tutti i numeri interi esistenti da -2'147'483'648 a +2'147'483'647. Analogamente un tipo generic può assumere come valore un altro tipo e, quindi, astrae tutti i possibili tipi usabili in quella classe/metodo/proprietà eccetera. È come dire: definiamo la funzione Somma(A, B), dove A e B sono di un tipo T che non conosciamo. Quando utilizziamo la funzione Somma, oltre a specificare i parametri richiesti, dobbiamo anche "dire" di quale tipo essi siano (ossia immettere in T non un valore ma un tipo): in questo modo, definendo un solo metodo, potremo eseguire somme tra interi, decimali, stringhe, date, file, classi, eccetera... In VB.NET, l'operazione di specificare un tipo per un entità generic si attua con questa sintassi:[NomeEntità](Of [NomeTipo])Dato i generics di possono applicare ad ogni entità del .NET (metodi, classi, proprietà, strutture, interfacce, delegate, eccetera...), ho scritto solo "NomeEntità" per indicare il nome del target a cui si applicano. Il prossimo esempio mostra come i generics, usati sulle liste, possano aumentare di molto le performance di un programma. 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) NextInfatti, 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) NextE 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 ModuleSul 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 StandardUna volta imparato a dichiarare e scrivere entità generics, sarà anche altrettanto semplice usare quelli esistenti, perciò iniziamo col dare le prime informazioni su come scrivere, ad esempio, una classe generics.Una classe generics si riferisce ad un qualsiasi tipo T che non possiamo conoscere al momento dela scrittura del codice, ma che il programmatore specificherà all'atto di dichiarazione di un oggetto rappresentato da questa classe. Il fatto che essa sia di tipo generico indica che anche i suoi membri, molto probabilmente, avranno lo stesso tipo: più nello specifico, potrebbero esserci campi di tipo T e metodi che lavorano su oggetti di tipo T. Se nessuna di queste due condizioni è verificata, allora non ha senso scrivere una classe generics. Ma iniziamo col vedere un semplice esempio: Module Module1 'Collezione generica che contiene un qualsiasi tipo T di 'oggetto. T si dice "tipo generic aperto" Class Collection(Of T) 'Per ora limitiamoci a dichiarare un array interno 'alla classe. 'Vedremo in seguito che è possibile ereditare da 'una collezione generics già esistente. 'Notate che la variabile è di tipo T: una volta che 'abbiamo dichiarato la classe come generics su un tipo T, 'è come se avessimo "dichiarato" l'esistenza di T 'come tipo fittizio. Private _Values() As T 'Restituisce l'Index-esimo elemento di Values (anch'esso 'è di tipo T) Public Property Values(ByVal Index As Int32) As T Get If (Index >= 0) And (Index < _Values.Length) Then Return _Values(Index) Else Throw New IndexOutOfRangeException() End If End Get Set(ByVal value As T) If (Index >= 0) And (Index < _Values.Length) Then _Values(Index) = value Else Throw New IndexOutOfRangeException() End If End Set End Property 'Proprietà che restituiscono il primo e l'ultimo 'elemento della collezione Public ReadOnly Property First() As T Get Return _Values(0) End Get End Property Public ReadOnly Property Last() As T Get Return _Values(_Values.Length - 1) End Get End Property 'Stampa tutti i valori presenti nella collezione a schermo. 'Su un tipo generic è sempre possibile usare 'l'operatore Is (ed il suo corrispettivo IsNot) e 'confrontarlo con Nothing. Se si tratta di un tipo value 'l'uguaglianza con Nothing sarà sempre falsa. Public Sub PrintAll() For Each V As T In _Values If V IsNot Nothing Then Console.WriteLine(V.ToString()) End If Next End Sub 'Inizializza la collezione con Count elementi, tutti del 'valore DefaultValue Sub New(ByVal Count As Int32, ByVal DefaultValue As T) If Count < 1 Then Throw New ArgumentOutOfRangeException() End If ReDim _Values(Count - 1) For I As Int32 = 0 To _Values.Length - 1 _Values(I) = DefaultValue Next End Sub End Class Sub Main() 'Dichiara quattro variabili contenenti quattro nuovi 'oggetti Collection. Ognuno di questi, però, 'è specifico per un solo tipo che decidiamo 'noi durante la dichiarazione. String, Int32, Date 'e Person, ossia i tipi che stiamo inserendo nel tipo 'generico T, si dicono "tipi generic collegati", 'poiché collegano il tipo fittizio T con un 'reale tipo esistente Dim Strings As New Collection(Of String)(10, "null") Dim Integers As New Collection(Of Int32)(5, 12) Dim Dates As New Collection(Of Date)(7, Date.Now) Dim Persons As New Collection(Of Person)(10, Nothing) Strings.Values(0) = "primo" Integers.Values(3) = 45 Dates.Values(6) = New Date(2009, 1, 1) Persons.Values(3) = New Person("Mario", "Rossi", Dates.Last) Strings.PrintAll() Integers.PrintAll() Dates.PrintAll() Persons.PrintAll() Console.ReadKey() End Sub End ModuleOgnuna della quattro variabili del sorgente contiene un oggetto di tipo Collection, ma tali oggetti non sono dello stesso tipo, poiché ognuno espone un differente tipo generics collegato. Quindi, nonostante si tratti sempre della stessa classe Collection, Collection(Of Int32) e Collection(Of String) sono a tutti gli effetti due tipi diversi: è come se esistessero due classi in cui T è sostituito in una da Int32 e nell'altra da String. Per dimostrare la loro diversità, basta scrivere: Console.WriteLine(Strings.GetType() Is Integers.GetType()) 'Output : False Metodi Generics e tipi generics collegati implicitiSe si decide di scrivere un solo metodo generics, e di focalizzare su di esso l'attenzione, solo accanto al suo nome apparirà la dichiarazione di un tipo generics aperto, con la consueta clausola "(Of T)". Anche se fin'ora ho usato come nome solamente T, nulla vieta di specificare un altro identificatore valido (ad esempio Pippo): tuttavia, è convenzione che il nome dei tipi generics aperti sia Tn (con n numero intero, ad esempio T1, T2, T3, eccetra...) o, in caso contrario, che inizi almeno con la lettera T (ad esempio TSize, TClass, eccetera...).Sub [NomeProcedura](Of T)([Parametri]) '... End Sub Function [NomeFunzione](Of T)([Parametri]) As [TipoRestituito] '... End FunctionEcco un semplice esempio: Module Module1 'Scambia i valori di due variabili, passate 'per indirizzo Public Sub Swap(Of T)(ByRef Arg1 As T, ByRef Arg2 As T) Dim Temp As T = Arg1 Arg1 = Arg2 Arg2 = Temp 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" 'Nelle prossime chiamate, Swap non presenta un 'tipo generics collegato: il tipo viene dedotto dai 'tipi degli argomenti '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 generic collegato Swap(Of Double)(Z, X) 'Soluzione 2: si converte Z in double esplicitamente Swap(CDbl(Z), X) Console.ReadKey() End Sub End Module Generics multipliQuando, anziché un solo tipo generics, se ne specificano due o più, si parla di genrics multipli. La dichiarazione avviene allo stesso modo di come abbiamo visto precedentemente e i tipi vengono separati da una virgola: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 ModuleNotate che è anche possibile creare una relazione tra due relazioni (e la cosa diventa complicata): 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") Dim StudentTeacherRelation As Relation(Of Student, Teacher) Dim StudentClassRelation As Relation(Of Student, String) Dim Relations As Relation(Of Relation(Of Student, Teacher), Relation(Of Student, String)) StudentTeacherRelation = New Relation(Of Student, Teacher)(S, T) StudentClassRelation = New Relation(Of Student, String)(S, "5A") Relations = New Relation(Of Relation(Of Student, Teacher), Relation(Of Student, String))(StudentTeacherRelation, StudentClassRelation) 'Relations.FirstObject.FirstObject ' > Student "Pinco Pallino" 'Relations.FirstObject.SecondObject ' > Teacher "Mario Rossi" 'Relations.SecondObject.FirstObject ' > Student "Pinco Pallino" 'Relations.SecondObject.SecondObject ' > String "5A" Alcune regole per l'uso dei Generics
C#, TypeScript, java, php, EcmaScript (JavaScript), Spring, Hibernate, React, SASS/LESS, jade, python, scikit, node.js, redux, postgres, keras, kubernetes, docker, hexo, etc...
|