Area di riferimento
- Developing applications that use system types and collections
- Manage data in a .NET Framework application by using the .NET Framework 2.0 system types
- Generic types
Generic Types
I generics sono un meccanismo offerto dal CLR e supportato dalla sintassi dei linguaggi di programmazione che fornisce una modo di riutilizzare algoritmi.
Ritengo che i generics siano una delle caratteristiche del framework 2.0 che maggiormente ha incrementato la produttivita dello sviluppatore.
L'idea alla base è avere la possibilità di definire algoritmi indipendenti dal tipo di dato su cui lavorano, permettendo di riutilizzarne la logica.
Il paradigma di programmazione generica permette anche di creare strutture dati generiche in particolare collections.
Vediamo come prima del .NET Framework 2.0 potevamo creare una struttura dati a Pila in grado di memorizzare qualsiasi elemento.
(per ora non considero eventuali problemi legati alla pila piena o alla pila vuota che tratterò in un successivo post sulla gestione delle eccezioni)
// Generica Pila con dimensioni limitate
public class OPila
{
private int MAX; // numero massimo di elementi
private int cont = 0; // numero corrente di elementi nella pila
private object[] elementi; // vettore che conterrà gli elementi
// Costruisco una pila che può contenere al massimo N elementi
public OPila(int max)
{
MAX = max;
elementi = new object[MAX];
}
// inserisco un elemento nella pila
public void push(object dato)
{
elementi[cont] = dato;
cont++;
}
// estraggo un elemento dalla pila
public object pop()
{
cont--;
object dato = elementi[cont];
return dato;
}
}
Si sfruttava il tipo di dato Object.
Utilizzando i generics del .NET Framework 2.0 invece possiamo scrivere un codice di questo tipo:
// Generica Pila con dimensioni limitate
public class Pila<T>
{
private int MAX; // numero massimo di elementi
private int cont = 0; // numero corrente di elementi nella pila
private T[] elementi; // vettore che conterrà gli elementi
// Costruisco una pila che può contenere al massimo N elementi
public Pila(int max)
{
MAX = max;
elementi = new T[MAX];
}
// inserisco un elemento nella pila
public void push(T dato)
{
elementi[cont] = dato;
cont++;
}
// estraggo un elemento dalla pila
public T pop()
{
cont--;
T dato = elementi[cont];
return dato;
}
}
Vediamo un esempio di utilizzo di queste due classi per memorizzare interi (value-type) :
OPila opila = new OPila(10);
opila.push(1); // boxing
opila.push(2); // boxing
int primo = (int) opila.pop(); // unboxing, cast
int secondo = (int) opila.pop(); // unboxing, cast
Pila<int> pila = new Pila<int>(10);
pila.push(1);
pila.push(2);
int primo = pila.pop();
int secondo = pila.pop();
Vediamo un esempio di utilizzo di queste due classi per memorizzare Studenti (reference-type) :
OPila studenti = new OPila(10);
studenti.push(new Studente("Andrea"));
studenti.push(new Studente("Stefano"));
Studente s1 = (Studente) studenti.pop(); // cast
Studente s2 = (Studente) studenti.pop(); // cast
Console.WriteLine(s1);
Console.WriteLine(s2);
Pila<Studente> studenti = new Pila<Studente>(10);
studenti.push(new Studente("Andrea"));
studenti.push(new Studente("Stefano"));
Studente s1 = studenti.pop();
Studente s2 = studenti.pop();
Console.WriteLine(s1);
Console.WriteLine(s2);
Perchè utilizzare i generics ?
- Sicurezza del tipo
Quando un algoritmo generico è utilizzato con uno specifico tipo, il compilatore e il CLR assicurano che solo gli oggetti compatibili con quel tipo specifico possano essere utilizzati.
Nell'esempio Pila<int> può essere utilizzata solamente per inserire ed estrarre interi pena un errore di compilazione. L'utilizzo di object invece non pone limitazioni e l'unico modo per rendersi conto di un eventuale errore è a tempo di esecuzione !
- Codice più pulito
La sicurezza del tipo offerta dai generics evita la necessità di effettuare dei cast espliciti rendendo quindi il codice più facile da scrivere e mantenere.
- Migliori Performance
L'utilizzo di object nel caso di value-types comporta l'esecuzione di notevoli operazioni di boxing e unboxing le quali sappiamo incidono molto sulle performance del nostro codice.
Utilizzare i generics permette di evitare questo notevole overhead.
Sottolineo la possibilità di utilizzare la parola chiave using di C# per definire un alias a tipi generici specifici in modo da rendere il codice meno confuso:
using PilaInteri = namespaceProgetto.Pila<int>;
using PilaStudenti = namespaceProgetto.Pila<namespaceProgetto.Studente>;
dove namespaceProgetto deve essere sostituito con il nome del namespace all'interno del quale sono definite le classi Pila e Studente.