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
- Exception Classes
Exception Classes
Le eccezioni sono un potente meccanismo per scrivere codice più robusto e manutenibile.
Riprendiamo il codice di esempio utilizzato nel post "[70-536] - Generic Types":
// 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;
}
}
Se proviamo a lanciare il seguente codice:
Pila<int> pila = new Pila<int>(2);
pila.push(1);
pila.push(2);
pila.push(3); // IndexOutOfRangeException
notiamo che alla terza invocazione del metodo push viene sollevata una eccezione dovuta al fatto che il codice del metodo push sta tentando di oltrepassare i limiti dell'array elementi.
Se questa eccezione non è opportunamente gestita l'applicazione viene abortita.
Possiamo gestire le eccezioni utilizzando codice simile al seguente:
try
{
Pila<int> pila = new Pila<int>(2);
pila.push(1);
pila.push(2);
pila.push(3);
}
catch (IndexOutOfRangeException ex)
{
// Codice per gestire l'eccezione di tipo IndexOutOfRangeException
// ex contiene informazioni relative all'eccezione sollevata
}
catch (Exception ex)
{
// Codice per gestire tutti gli altri tipi di eccezione
// ex contiene informazioni relative all'eccezione sollevata
}
finally
{
// codice che viene sempre eseguito
}
All'interno dei blocchi catch verrà inserito il codice relativo alla gestione di uno specifico tipo di eccezione. E' importante notare che tutti le eccezioni CLS-Compliant derivano da System.Exception. Il blocco finally infine contiene del codice la cui esecuzione viene garantita sia in presenza che in assenza di eccezioni. Tipicamente il codice in un blocco finally esegue operazioni di pulizia richieste a causa di azioni intraprese nel blocco try.
Quando si realizza una libreria di classi è opportuno realizzare anche una gerarchia di eccezioni in modo da sollevarle quando necessario per fornire maggiori informazioni all'utente.
Vediamo quindi di creare questa gerarchia nel caso della classe Pila
// Classe base per eccezioni della Pila
public abstract class PilaException : Exception
{
public PilaException() : base("Eccezione di Pila generica") {}
public PilaException(string msg) : base(msg) {}
public PilaException(string msg, Exception innerException) : base(msg, innerException) {}
}
// Tipo di eccezione che rappresenta la situazione di coda piena
public class PilaPienaException : PilaException
{
public PilaPienaException() : base("Eccezione di Pila Piena") {}
public PilaPienaException(string msg) : base(msg) {}
public PilaPienaException(string msg, Exception innerException) : base(msg, innerException) {}
}
// Tipo di eccezione che rappresenta la situazione di coda vuota
public class PilaVuotaException : PilaException
{
public PilaVuotaException() : base("Eccezione di Pila Piena") {}
public PilaVuotaException(string msg) : base(msg) {}
public PilaVuotaException(string msg, Exception innerException) : base(msg, innerException) {}
}
Dovremo quindi modificare il codice della classe Pila in modo da sollevare le eccezioni quando necessario:
public class Pila<T>
{
private int MAX;
private int cont = 0;
private T[] elementi;
public Pila(int max)
{
MAX = max;
elementi = new T[MAX];
}
public void push(T dato)
{
if (cont == MAX)
{
throw new PilaPienaException("Errore: la pila è piena !");
}
elementi[cont] = dato;
cont++;
}
public T pop()
{
if (cont == 0)
{
throw new PilaVuotaException("Errore: la pila è vuota !");
}
cont--;
T dato = elementi[cont];
return dato;
}
}
Vediamo un utilizzo della nuova classe pila:
Pila<int> pila = new Pila<int>(2);
try
{
pila.push(1);
pila.push(2);
pila.push(3); // viene sollevata PilaPienaException
pila.pop();
pila.pop();
}
catch (PilaPienaException ex)
{
Console.WriteLine(ex.Message); // viene eseguita questa linea di codice
}
catch (PilaVuotaException ex)
{
Console.WriteLine(ex.Message);
}
La creazione della classe base permette di catturare contemporaneamente tutte le eccezioni relative alla classe Pila grazie a un codice come il seguente:
Pila<int> pila = new Pila<int>(2);
try
{
pila.push(1);
pila.push(2);
pila.push(3); // viene sollevata PilaPienaException
pila.pop();
pila.pop();
}
catch (PilaException ex)
{
Console.WriteLine(ex.Message); // viene eseguita questa linea di codice
}
Utilizzare le eccezioni offre diversi vantaggi:
- permettono di tenere il codice di pulizia in una locazione dedicata avendo la garanzia che questa pulizia sarà eseguita; questo rende l'applicazioni più facile da scrivere, capire e mantenere
- capacità di localizzare e risolvere bug nel codice grazie alla funzionalità di tracing dello stack
I vantaggi che derivano dall'utilizzo delle eccezioni bilanciano completamente il problema di una leggera diminuzione delle performance.