Lo scopo di ogni programmatore ad oggetti e di scrivere del codice che sia il più riutilizzabile possibile. Questo perchè, oltre ad un minore tempo di sviluppo, diminuisce il numero di errore che andremo a scrivere e renderà più facile la loro individuazione e correzione. E' risaputo infatti che la prima regola del programmatore è 'Più codice scrivi, più errori commetti', quando poi la maggior parte di esso è una ripetizione infinita di operazioni molto simili la cosa diviene insostenibile. Visual Basic 2005, così come tutti i linguaggi del Framework 2.0, sono ora in grado di gestire oggetti 'generici', i 'Generics' appunto.
I 'Generics' sono speciali classi, metodi ed insiemi in cui il tipo non viene definito ma passato come parametro ad ogni sua creazione, in questo modo quel particolare algoritmo può essere utilizzato per infiniti tipi, sia base che custom. Così facendo non solo ridurremo le righe di codice ma avremo sempre un insieme 'Type-Safe' che, come vedremo nel dettaglio, renderà più sicura la nostra applicazione.
Lo spazio dei nomi del Framework che contiene gli insiemi 'Generics' è la 'Systems.Collections.Generic'. Qui troveremo:
- Collection, equivalente generico di CollectionBase.
- Dictionary, equivalente generico di HashTable.
- LinkedList, non ha equivalente non generico.
- List, equivalente generico di ArrayList.
- Queue (FIFO), stesso nome dell'equivalente non generico.
- ReadOnlyCollection, equivalente generico di ReadOnlyCollectionBase.
- SortedDictionary, non equivalente non generico.
- SortedList, stesso nome dell'equivalente non generico.
- Stack (LIFO), stesso nome dell'equivalente non generico.
È l'utilizzo di queste nuove classi che apporterà il maggiore vantaggio alla produttività sebbene, come detto, i 'Generics' possano essere usati ben oltre i soli insiemi.
La spiegazione dei 'Generics' non può non essere accompagnata da degli esempi che facciano capire chiaramente il loro utilizzo.
Immaginiamo di dover gestire un sito di commercio elettronico e di aver creato un oggetto per ogni singolo attore della catena di vendita. Avremo quindi a nostra disposizione svariati oggetti tra cui Prodotto, Articolo (che contiene una o più specifiche del nostro prodotto), e Carrello.
Senza i 'Generics' per tenere traccia dei prodotti inseriti nel Carrello avremmo utilizzato un 'ArrayList'. L'ArrayList ci permette di inserire al suo interno N elementi trattandoli come oggetti qualsiasi (Object). Cosa accadrebbe però se al posto di un oggetto Prodotto gli venisse inserito un oggetto Articolo? Purtroppo niente. Seppure l'operazione secondo la nostra logica di progettazione sia errata a livello di programmazione risulta corretta: abbiamo inserito un oggetto in un contenitore di oggetti. L'eventualità che questo possa accadere comporta, per non correre rischi più avanti, di dovere controllare se l'oggetto passato è quello che ci aspettavamo o meno. Il tutto comporterà un aumento del codice da dovere scrivere e rivedere ogni volta che viene effettuata una modifica ai vari Oggetti per verificare che i nostri controlli siano ancora validi.
Esaminiamo il seguente codice e vediamo i problemi che andremo ad incontrare con ArrayList e osserviamo come i 'Generics' risolvano una volta per tutte questi inconvenienti.
Partiamo dal codice di Prodotto e di Articolo:
Public Class Prodotto
Dim _nome As String
Private Sub New()
' non visibile
End Sub
Public Sub New(ByVal nome As String)
_nome = nome
End Sub
Public Property GetNomeProdotto() As String
Get
Return _nome
End Get
Private Set(ByVal value As String)
_nome = value
End Set
End Property
End Class
Public Class Articolo
Dim _nome As String
Friend Sub New()
' non visibile
End Sub
Public Sub New(ByVal nome As String)
_nome = nome
End Sub
Public Property GetNomeArticolo() As String
Get
Return _nome
End Get
Private Set(ByVal value As String)
_nome = value
End Set
End Property
End Class
Il codice è abbastanza semplice, tutte e due le classi hanno un metodo New che accetta una string come nome ed una proprietà che restituisce il nome assegnato in creazione o con la stessa proprietà. Le proprietà utilizzano una nuova caratteristica di Visual Basic 2005, la possibilità di differenziare il livello di accesso in maniera diversa per 'Get' e 'Set'.
Adesso inseriamo delle istanze di esempio nel nostro ipotetico carrello e poi riprendiamo il valore del nome per mostrarlo a video, magari associato ad una label.
Dim NomeProdotto As String = ""
' creo i prodotti
Dim prodotto1 As New Prodotto("Primo")
Dim prodotto2 As New Prodotto("Secondo")
' creo un articolo
Dim articolo1 As New Articolo("Primo")
' metto tutto dentro il carrello
Dim MyCarrello As New ArrayList()
MyCarrello.Add(prodotto1)
MyCarrello.Add(prodotto2)
MyCarrello.Add(articolo1)
For i As Integer = 0 To MyCarrello.Count – 1
NomeProdotto += MyCarrello.Item(i).GetNomeProdotto() & "<br>"
Next
Come notiamo abbiamo inserito per errore un articolo all'interno del nostro carrello quindi la nostra applicazione andrà in errore non appena, scorrendo l'ArrayList, tenterà di recuperarne il nome dato poiché Articolo non espone nessuna proprietà 'GetNomeProdotto'. A prima vista potremmo creare un oggetto carrello personalizzato, in questo modo il compilatore ci avvertirebbe dell'errore:
Imports System.Collections.Generic
Public Class CarrelloVecchio
Dim lista As ArrayList
Sub New()
lista = New ArrayList
End Sub
Public Function Add(ByVal value As Prodotto) As Integer
Return lista.Add(value)
End Function
Public Sub Remove(ByVal value As Prodotto)
lista.Remove(value)
End Sub
Public ReadOnly Property Item(ByVal index As Integer) As Prodotto
Get
Return CType(lista.Item(index), Prodotto)
End Get
End Property
End Class
E nella nostra pagina:
Dim mycarrello As New CarrelloVecchio
mycarrello.Add(prodotto1)
mycarrello.Add(prodotto2)
mycarrello.Add(articolo1) ' Errore!!
Proseguendo però dovremo creare un oggetto del tutto simile, o inserire lo stesso codice in Prodotto stesso, per le istanze Articolo che inseriremo nelle istanze Prodotto ed avremo quindi il doppio del codice da mantenere per una operazione identica.
Per ovviare al problema creiamo una classe carrello generica come la seguente:
Imports System.Collections.Generic
Public Class CarrelloNuovo(Of MioTipo)
Inherits CollectionBase
Public Function Add(ByVal value As MioTipo) As Integer
Return List.Add(value)
End Function
Public Sub Remove(ByVal value As MioTipo)
List.Remove(value)
End Sub
Public ReadOnly Property Item(ByVal index As Integer) As MioTipo
Get
Return CType(List.Item(index), MioTipo)
End Get
End Property
End Class
Notiamo il modo di dichiarare una classe generica e la parola chiave 'Of' che specifica il nome del tipo all'interno della classe. Ogni classe generica può avere uno o più elementi generici. Eseguiamo la stessa operazione di prima nella nostra pagina ASPX:
Dim mycarrelloProdotti As New CarrelloNuovo(Of Prodotto)
mycarrelloProdotti.Add(prodotto1)
mycarrelloProdotti.Add(prodotto2)
mycarrelloProdotti.Add(articolo1)' Errore
A questo punto il compilatore ci segnalerà l'errore di inserimento così come avviene con la classe CarrelloVecchio. Adesso però creiamo un altro carrello per le istanze Articolo modificando soltanto il tipo nella dichiarazione:
Dim mycarrelloArticoli As New CarrelloNuovo(Of Articolo)
mycarrelloArticoli.Add(articolo1)
Il tutto funzionerà alla perfezione!
Ancora, volendo potremmo imporre dei limiti (vincoli) alla nostra classe generica semplicemente definendo la classe o l'interfaccia che il tipo da noi scelto debba ereditare/implementare. Per esempio:
Public Class CarrelloVincolato(Of elemento As ISerializable)
In questo modo la nostra classe accetterà tutti i tipi che implementano l'interfaccia 'ISerializable'.
Abbiamo visto come con lo stesso codice si possa ovviare ad un problema del tutto simile in modo sicuro e veloce. Fin qui abbiamo detto perchè il codice è più sicuro ma perchè è più veloce? Semplice, quando si inserisce un oggetto in un ArrayList diviene di tipo Object e quando lo andiamo a recuperare bisogna quindi effettuare il casting al suo tipo di origine. Grazie ai 'Generics' l'oggetto contenitore lavora con il tipo usato nella dichiarazione quindi il casting non è più necessario.
Le potenzialità dei 'Generics' sono enormi, possiamo finalmente creare del codice realmente riutilizzabile per un'infinità di situazioni senza correre il rischio di lasciare qualcosa di errato in giro perchè il compilatore è in grado di avvertirci quando qualcosa non va. Adesso grazie ai 'Generics' possiamo usare in Visual Basic 2005, e quindi in C#, la cosiddetta programmazione parametrica, implementata in Java 5 o 1.5 che dir si voglia e presente ormai da anni in C++.