Gestione delle Eccezioni in C#
Rendere i programmi robusti di fronte a errori e situazioni impreviste
⚠️ Introduzione alle Eccezioni
Le eccezioni sono eventi che si verificano durante l’esecuzione di un programma che
interrompono il normale flusso di istruzioni. Rappresentano errori o situazioni anomale che, se non gestite,
possono causare l’arresto improvviso dell’applicazione. La gestione delle eccezioni in C# permette di
anticipare questi problemi e risolverli in modo elegante.
💡 Perché Gestire le Eccezioni?
Immagina un programma che chiede all’utente di inserire un numero. Se l’utente inserisce del testo
invece di un numero, il programma potrebbe crashare improvvisamente. Con la gestione
delle eccezioni, puoi intercettare questo errore e chiedere gentilmente all’utente di riprovare,
mantenendo il programma in esecuzione.
🔧 Struttura Base di try-catch-finally
La gestione delle eccezioni in C# utilizza i blocchi try, catch e finally:
try
{
// Codice che potrebbe generare un'eccezione
}
catch (TipoEccezione ex)
{
// Codice che gestisce l'eccezione
}
finally
{
// Codice che viene eseguito SEMPRE, indipendentemente
// dal verificarsi di un'eccezione
}
- Il codice all’interno del blocco
tryviene eseguito normalmente - Se si verifica un’eccezione, l’esecuzione del blocco
tryviene interrotta - Il controllo passa al primo blocco
catchche corrisponde al tipo di eccezione - Il blocco
finallyviene sempre eseguito (utile per pulizia risorse)
try
{
Console.Write("Inserisci un numero: ");
string input = Console.ReadLine();
int numero = int.Parse(input); // Potrebbe generare eccezione
Console.WriteLine($"Hai inserito: {numero}");
}
catch (FormatException)
{
Console.WriteLine("❌ Errore: devi inserire un numero valido!");
}
finally
{
Console.WriteLine("✅ Operazione completata.");
}
🚨 Tipi di Eccezioni Comuni
NullReferenceException
Si verifica quando si tenta di accedere a un metodo o una proprietà di un oggetto che è null:
string nome = null;
try
{
int lunghezza = nome.Length; // Genera NullReferenceException
Console.WriteLine($"Lunghezza: {lunghezza}");
}
catch (NullReferenceException ex)
{
Console.WriteLine($"❌ L'oggetto è null: {ex.Message}");
}
IndexOutOfRangeException
Si verifica quando si tenta di accedere a un indice non valido di un array:
int[] numeri = { 1, 2, 3 };
try
{
int valore = numeri[5]; // Array ha solo indici 0, 1, 2
Console.WriteLine(valore);
}
catch (IndexOutOfRangeException ex)
{
Console.WriteLine($"❌ Indice non valido: {ex.Message}");
}
DivideByZeroException
Si verifica quando si tenta di dividere per zero:
try
{
int risultato = 10 / 0; // Impossibile dividere per zero
Console.WriteLine(risultato);
}
catch (DivideByZeroException ex)
{
Console.WriteLine($"❌ Impossibile dividere per zero: {ex.Message}");
}
FormatException
Si verifica quando si tenta di convertire una stringa in un formato non valido:
try
{
int numero = int.Parse("abc"); // "abc" non è un numero
Console.WriteLine(numero);
}
catch (FormatException ex)
{
Console.WriteLine($"❌ Formato non valido: {ex.Message}");
}
| Eccezione | Quando si Verifica | Esempio Tipico |
|---|---|---|
NullReferenceException |
Accesso a oggetto null | string.Length su null |
IndexOutOfRangeException |
Indice array non valido | array[10] su array di 5 elementi |
DivideByZeroException |
Divisione per zero | 10 / 0 |
FormatException |
Conversione formato errato | int.Parse("abc") |
ArgumentException |
Argomento non valido | Parametro fuori range |
🎯 Gestione di Più Eccezioni
È possibile gestire diversi tipi di eccezioni con più blocchi catch.
Questo permette di fornire messaggi di errore specifici per ogni situazione.
try
{
Console.Write("Inserisci un numero: ");
string input = Console.ReadLine();
int numero = int.Parse(input);
int risultato = 100 / numero;
Console.WriteLine($"Risultato: {risultato}");
}
catch (FormatException)
{
Console.WriteLine("❌ L'input non è un numero valido.");
}
catch (DivideByZeroException)
{
Console.WriteLine("❌ Non è possibile dividere per zero.");
}
catch (Exception ex)
{
// Cattura qualsiasi altra eccezione non gestita sopra
Console.WriteLine($"❌ Errore generico: {ex.Message}");
}
finally
{
Console.WriteLine("✅ Operazione completata.");
}
I blocchi catch devono essere disposti dal più specifico al più generale.
Se posizioni catch (Exception) prima di altri blocchi più specifici, questi non verranno
mai raggiunti perché Exception è la classe base per tutte le eccezioni.
// ✅ CORRETTO - Dal più specifico al più generale
try { /* ... */ }
catch (FormatException ex) { /* ... */ }
catch (DivideByZeroException ex) { /* ... */ }
catch (Exception ex) { /* ... */ }
// ❌ ERRATO - Exception cattura tutto
try { /* ... */ }
catch (Exception ex) { /* ... */ }
catch (FormatException ex) { /* QUESTO NON VERRÀ MAI ESEGUITO */ }
catch (DivideByZeroException ex) { /* QUESTO NON VERRÀ MAI ESEGUITO */ }
📤 Propagazione delle Eccezioni
Se un’eccezione non viene gestita in un metodo, viene propagata al metodo chiamante.
Questo meccanismo permette di gestire gli errori a livelli superiori del programma.
static void Main()
{
try
{
MetodoA();
}
catch (Exception ex)
{
Console.WriteLine($"❌ Eccezione catturata in Main: {ex.Message}");
}
}
static void MetodoA()
{
Console.WriteLine("Eseguo MetodoA...");
MetodoB(); // L'eccezione verrà propagata a Main
}
static void MetodoB()
{
Console.WriteLine("Eseguo MetodoB...");
throw new Exception("Errore critico in MetodoB!");
}
// Output:
// Eseguo MetodoA...
// Eseguo MetodoB...
// ❌ Eccezione catturata in Main: Errore critico in MetodoB!
- Main() chiama MetodoA()
- MetodoA() chiama MetodoB()
- MetodoB() genera un’eccezione con
throw - L’eccezione risale a MetodoA() (non gestita)
- L’eccezione risale a Main() dove viene catturata
🎪 Lancio di Eccezioni Personalizzate
È possibile lanciare eccezioni esplicitamente con la parola chiave throw.
Questo è utile per validare input o parametri.
public void Deposita(decimal importo)
{
if (importo <= 0)
{
throw new ArgumentException("L'importo deve essere positivo");
}
saldo += importo;
Console.WriteLine($"Depositati {importo}€. Saldo: {saldo}€");
}
// Utilizzo:
try
{
Deposita(-50); // Genera ArgumentException
}
catch (ArgumentException ex)
{
Console.WriteLine($"❌ Errore: {ex.Message}");
}
Eccezioni Personalizzate
È anche possibile creare eccezioni personalizzate estendendo la classe Exception:
public class SaldoInsufficienteException : Exception
{
public decimal SaldoAttuale { get; }
public decimal ImportoRichiesto { get; }
public SaldoInsufficienteException(string message,
decimal saldoAttuale,
decimal importoRichiesto)
: base(message)
{
SaldoAttuale = saldoAttuale;
ImportoRichiesto = importoRichiesto;
}
}
// Utilizzo:
public void Preleva(decimal importo)
{
if (importo > saldo)
{
throw new SaldoInsufficienteException(
"Saldo insufficiente per il prelievo",
saldo,
importo);
}
saldo -= importo;
}
// Gestione:
try
{
Preleva(1000);
}
catch (SaldoInsufficienteException ex)
{
Console.WriteLine($"❌ {ex.Message}");
Console.WriteLine($"Saldo attuale: {ex.SaldoAttuale}€");
Console.WriteLine($"Importo richiesto: {ex.ImportoRichiesto}€");
}
🚀 Esempio Pratico: Validazione Input Utente
Vediamo un esempio completo che dimostra come gestire le eccezioni per validare l'input utente
in modo robusto.
using System;
class Program
{
static void Main()
{
bool inputValido = false;
int eta = 0;
Console.WriteLine("=== REGISTRAZIONE UTENTE ===\n");
while (!inputValido)
{
Console.Write("Inserisci la tua età: ");
try
{
string input = Console.ReadLine();
eta = int.Parse(input);
if (eta < 0 || eta > 120)
{
throw new ArgumentOutOfRangeException(
"L'età deve essere compresa tra 0 e 120");
}
inputValido = true;
Console.WriteLine($"✅ Hai inserito: {eta} anni");
}
catch (FormatException)
{
Console.WriteLine("❌ Errore: devi inserire un numero intero.\n");
}
catch (ArgumentOutOfRangeException ex)
{
Console.WriteLine($"❌ Errore: {ex.Message}\n");
}
catch (Exception ex)
{
Console.WriteLine($"❌ Errore imprevisto: {ex.Message}\n");
}
}
try
{
string fascia = DeterminaFasciaEta(eta);
Console.WriteLine($"\n📊 Appartieni alla fascia d'età: {fascia}");
}
catch (Exception ex)
{
Console.WriteLine($"❌ Errore nel determinare la fascia: {ex.Message}");
}
}
static string DeterminaFasciaEta(int eta)
{
if (eta < 0)
{
throw new ArgumentException("L'età non può essere negativa");
}
if (eta <= 12) return "Bambino";
if (eta <= 19) return "Adolescente";
if (eta <= 65) return "Adulto";
return "Anziano";
}
}
- Loop
whilecontinua fino a input valido - Gestione separata per
FormatExceptioneArgumentOutOfRangeException catch (Exception)finale per errori imprevisti- Validazione sia nel metodo principale che in
DeterminaFasciaEta()
✨ Best Practices nella Gestione delle Eccezioni
Non catturare eccezioni che non sei in grado di gestire in modo significativo.
A volte è meglio lasciare che l'eccezione si propaghi a un livello superiore dove
può essere gestita appropriatamente.
// ❌ SCONSIGLIATO - Cattura e ignora
try
{
// Operazione critica
}
catch (Exception)
{
// Non fare nulla - "swallow exception"
}
// ✅ CONSIGLIATO - Lascia propagare o gestisci
try
{
// Operazione critica
}
catch (SpecificException ex)
{
// Log dell'errore
Console.WriteLine($"Errore: {ex.Message}");
// Eventualmente ri-lancia
throw;
}
Le eccezioni dovrebbero essere riservate a situazioni eccezionali, non per il controllo
del flusso normale del programma. Usa if/else o metodi come TryParse
quando possibile.
// ❌ SCONSIGLIATO - Usa eccezioni per controllo
try
{
int numero = int.Parse(input);
}
catch (FormatException)
{
numero = 0; // Valore di default
}
// ✅ CONSIGLIATO - Usa TryParse
int numero;
if (!int.TryParse(input, out numero))
{
numero = 0; // Valore di default
}
Includi nel blocco try solo il codice che potrebbe generare l'eccezione
che ti interessa gestire, non l'intero metodo. Questo rende più chiaro quale codice
può generare errori.
// ❌ SCONSIGLIATO - try troppo ampio
try
{
Console.Write("Inserisci numero: ");
string input = Console.ReadLine();
int numero = int.Parse(input);
int risultato = Calcola(numero);
Console.WriteLine($"Risultato: {risultato}");
}
catch (FormatException) { /* ... */ }
// ✅ CONSIGLIATO - try mirato
Console.Write("Inserisci numero: ");
string input = Console.ReadLine();
int numero;
try
{
numero = int.Parse(input); // Solo questa riga può generare FormatException
}
catch (FormatException)
{
Console.WriteLine("Input non valido");
return;
}
int risultato = Calcola(numero);
Console.WriteLine($"Risultato: {risultato}");
Utilizza il blocco finally per liberare risorse come file, connessioni
di rete o database, assicurandoti che vengano chiuse correttamente anche in caso di eccezioni.
FileStream file = null;
try
{
file = new FileStream("dati.txt", FileMode.Open);
// Operazioni sul file
}
catch (IOException ex)
{
Console.WriteLine($"Errore I/O: {ex.Message}");
}
finally
{
// Questa sezione viene SEMPRE eseguita
if (file != null)
{
file.Close();
Console.WriteLine("File chiuso correttamente");
}
}
// Ancora meglio: usa using statement
using (FileStream file = new FileStream("dati.txt", FileMode.Open))
{
// Il file viene chiuso automaticamente
}
| Best Practice | Descrizione | Beneficio |
|---|---|---|
| Specifico → Generale | Ordina catch dal più specifico al più generale | Gestione precisa degli errori |
| Usa TryParse | Preferisci TryParse a Parse + try-catch | Migliori performance |
| Blocchi try piccoli | Include solo codice che può generare eccezioni | Codice più chiaro e manutenibile |
| Messaggi descrittivi | Fornisci messaggi di errore chiari | Debugging più facile |
| Usa finally | Pulizia risorse nel blocco finally | Previene memory leak |
💪 Esercizi Pratici
📝 Esercizio 1: Calcolatrice Sicura
Crea una calcolatrice che:
- Chiede all'utente due numeri
- Chiede l'operazione da eseguire (+, -, *, /)
- Gestisce tutti i possibili errori (formato, divisione per zero, ecc.)
- Permette di ripetere l'operazione
📝 Esercizio 2: Gestore Password
Crea un sistema che valida password con le seguenti regole:
- Lunghezza minima 8 caratteri
- Almeno una maiuscola
- Almeno una minuscola
- Almeno un numero
- Lancia eccezioni personalizzate per ogni regola violata
- Struttura try-catch-finally
- Tipi di eccezioni comuni
- Gestione multipla con catch
- Propagazione eccezioni
- Lancio e creazione eccezioni
- Catch: specifico → generale
- Usa TryParse quando possibile
- Finally per pulizia risorse
- Messaggi di errore chiari
- Non usare per controllo flusso
Lezione 10 del Corso C# Completo