Gestione delle Eccezioni in C#
Impara a gestire gli errori in modo professionale con try-catch-finally e creare codice robusto
🎯 Cosa Sono le Eccezioni?
Un’eccezione è un errore che si verifica durante l’esecuzione di un programma.
Quando si verifica un errore, il programma “lancia” (throw) un’eccezione. Se non viene gestita,
il programma si interrompe bruscamente (crash).
💡 Perché Gestire le Eccezioni?
Immagina un’applicazione bancaria: se l’utente inserisce testo invece di un numero,
l’app non deve crashare! Deve mostrare un messaggio chiaro e permettere di riprovare.
La gestione delle eccezioni rende il tuo software robusto e
user-friendly.
class Program
{
static void Main()
{
Console.Write("Inserisci un numero: ");
string input = Console.ReadLine();
// ❌ SE l'utente scrive "abc" invece di un numero...
int numero = int.Parse(input); // CRASH! FormatException
Console.WriteLine($"Il doppio è: {numero * 2}");
// Questa riga NON viene mai eseguita se c'è errore!
}
}
// Output se input = "abc":
// Unhandled exception. System.FormatException: Input string was not in a correct format.
// PROGRAMMA TERMINATO BRUSCAMENTE
class Program
{
static void Main()
{
Console.Write("Inserisci un numero: ");
string input = Console.ReadLine();
try
{
// Codice che potrebbe generare un'eccezione
int numero = int.Parse(input);
Console.WriteLine($"Il doppio è: {numero * 2}");
}
catch (FormatException)
{
// Gestione dell'errore
Console.WriteLine("❌ Errore: Devi inserire un numero valido!");
}
Console.WriteLine("Programma terminato normalmente.");
}
}
// Output se input = "abc":
// ❌ Errore: Devi inserire un numero valido!
// Programma terminato normalmente.
// ✅ NESSUN CRASH!
Il programma non crasha per errori comuni
Messaggi chiari invece di crash misteriosi
Informazioni dettagliate sull’errore
Software di qualità enterprise
🛡️ Try-Catch-Finally
La struttura try-catch-finally è il meccanismo principale per gestire le eccezioni in C#.
Il blocco try contiene il codice rischioso, catch gestisce l’errore.
try
{
// Codice che potrebbe generare un'eccezione
int[] numeri = { 1, 2, 3 };
Console.WriteLine(numeri[10]); // IndexOutOfRangeException!
}
catch (Exception ex)
{
// Gestione dell'errore
Console.WriteLine($"Si è verificato un errore: {ex.Message}");
}
// Il programma continua qui...
Gestisci diversi tipi di eccezioni in modo specifico.
try
{
Console.Write("Inserisci numero: ");
string input = Console.ReadLine();
int numero = int.Parse(input);
Console.Write("Dividi per: ");
int divisore = int.Parse(Console.ReadLine());
int risultato = numero / divisore;
Console.WriteLine($"Risultato: {risultato}");
}
catch (FormatException)
{
Console.WriteLine("❌ Devi inserire un numero valido!");
}
catch (DivideByZeroException)
{
Console.WriteLine("❌ Non puoi dividere per zero!");
}
catch (OverflowException)
{
Console.WriteLine("❌ Il numero è troppo grande!");
}
catch (Exception ex) // Catch generico sempre per ultimo!
{
Console.WriteLine($"❌ Errore imprevisto: {ex.Message}");
}
Il blocco finally viene SEMPRE eseguito, anche se c’è un’eccezione.
FileStream file = null;
try
{
file = new FileStream("dati.txt", FileMode.Open);
// Leggi file...
Console.WriteLine("File letto con successo");
}
catch (FileNotFoundException)
{
Console.WriteLine("❌ File non trovato!");
}
catch (IOException ex)
{
Console.WriteLine($"❌ Errore I/O: {ex.Message}");
}
finally
{
// Questo blocco viene SEMPRE eseguito
// Perfetto per chiudere risorse (file, connessioni, ecc.)
if (file != null)
{
file.Close();
Console.WriteLine("File chiuso correttamente");
}
}
Console.WriteLine("Programma terminato");
try
{
Console.WriteLine("1. Eseguo try");
throw new Exception("Errore!");
Console.WriteLine("Questa riga NON viene eseguita");
}
catch (Exception)
{
Console.WriteLine("2. Eseguo catch (c'è stata un'eccezione)");
}
finally
{
Console.WriteLine("3. Eseguo finally (SEMPRE eseguito)");
}
Console.WriteLine("4. Continuo con il resto del programma");
// Output:
// 1. Eseguo try
// 2. Eseguo catch (c'è stata un'eccezione)
// 3. Eseguo finally (SEMPRE eseguito)
// 4. Continuo con il resto del programma
Finally è essenziale per liberare risorse:
- Chiudere file aperti
- Chiudere connessioni database
- Liberare memoria allocata
- Rilasciare lock e semafori
Alternativa moderna: Usa using statement per gestione automatica
(lo vedremo dopo).
⚠️ Tipi Comuni di Eccezioni
C# fornisce molti tipi di eccezioni predefinite per situazioni diverse.
| Eccezione | Quando Si Verifica | Esempio |
|---|---|---|
FormatException |
Input in formato errato | int.Parse("abc") |
DivideByZeroException |
Divisione per zero | 10 / 0 |
IndexOutOfRangeException |
Indice array fuori range | array[100] (su array piccolo) |
NullReferenceException |
Accesso a oggetto null | string s = null; s.Length |
ArgumentNullException |
Parametro null non consentito | Metodo riceve null quando non deve |
ArgumentException |
Argomento non valido | Parametro fuori range ammissibile |
FileNotFoundException |
File non esistente | Apertura file inesistente |
IOException |
Errori input/output | Problemi lettura/scrittura file |
InvalidOperationException |
Operazione non valida | Chiamata metodo in stato non valido |
OverflowException |
Numero troppo grande/piccolo | Risultato supera limiti tipo |
// ===== FormatException =====
try
{
int numero = int.Parse("abc123"); // ❌ Formato non valido
}
catch (FormatException)
{
Console.WriteLine("Formato numero non valido");
}
// ===== NullReferenceException =====
try
{
string testo = null;
int lunghezza = testo.Length; // ❌ testo è null!
}
catch (NullReferenceException)
{
Console.WriteLine("Tentativo di usare oggetto null");
}
// ===== IndexOutOfRangeException =====
try
{
int[] array = { 1, 2, 3 };
Console.WriteLine(array[10]); // ❌ Indice non esiste
}
catch (IndexOutOfRangeException)
{
Console.WriteLine("Indice array fuori range");
}
// ===== ArgumentNullException =====
public void ProcessaStringa(string testo)
{
if (testo == null)
{
throw new ArgumentNullException(nameof(testo), "Il testo non può essere null");
}
// ... elaborazione
}
🚀 Lanciare Eccezioni (throw)
Puoi lanciare (throw) eccezioni manualmente per segnalare errori nel tuo codice.
Usa throw per lanciare un’eccezione.
public void ImpostaEtà(int età)
{
if (età < 0)
{
throw new ArgumentException("L'età non può essere negativa!", nameof(età));
}
if (età > 150)
{
throw new ArgumentException("L'età non è realistica!", nameof(età));
}
this.Età = età;
}
// Utilizzo
try
{
persona.ImpostaEtà(-5); // Lancia ArgumentException
}
catch (ArgumentException ex)
{
Console.WriteLine($"Errore: {ex.Message}");
// Output: Errore: L'età non può essere negativa!
}
Puoi catturare un’eccezione, fare qualcosa, e ri-lanciarla.
public void LeggiFile(string percorso)
{
try
{
string contenuto = File.ReadAllText(percorso);
// ... elaborazione
}
catch (FileNotFoundException ex)
{
// Log dell'errore
Console.WriteLine($"[LOG] File non trovato: {percorso}");
// Re-lancia l'eccezione per farla gestire a chi chiama
throw; // ← Ri-lancia la stessa eccezione
}
}
// Chiamante
try
{
LeggiFile("config.txt");
}
catch (FileNotFoundException)
{
Console.WriteLine("Configurazione non trovata, uso default");
}
Crea le tue eccezioni personalizzate per situazioni specifiche.
// Definizione eccezione custom
public class SaldoInsufficienteException : Exception
{
public decimal SaldoDisponibile { get; set; }
public decimal ImportoRichiesto { get; set; }
public SaldoInsufficienteException(decimal saldo, decimal importo)
: base($"Saldo insufficiente. Disponibile: €{saldo}, Richiesto: €{importo}")
{
SaldoDisponibile = saldo;
ImportoRichiesto = importo;
}
}
// Utilizzo
public class ContoBancario
{
public decimal Saldo { get; private set; }
public void Preleva(decimal importo)
{
if (importo > Saldo)
{
throw new SaldoInsufficienteException(Saldo, importo);
}
Saldo -= importo;
}
}
// Test
try
{
ContoBancario conto = new ContoBancario { Saldo = 100 };
conto.Preleva(150);
}
catch (SaldoInsufficienteException ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine($"Disponibile: €{ex.SaldoDisponibile}");
Console.WriteLine($"Richiesto: €{ex.ImportoRichiesto}");
}
🔧 Using Statement
Il using statement gestisce automaticamente la pulizia delle risorse,
garantendo che Dispose() venga chiamato anche in caso di eccezioni.
// ❌ SENZA using (manuale, rischioso)
StreamReader reader = null;
try
{
reader = new StreamReader("file.txt");
string contenuto = reader.ReadToEnd();
Console.WriteLine(contenuto);
}
catch (IOException ex)
{
Console.WriteLine($"Errore: {ex.Message}");
}
finally
{
if (reader != null)
{
reader.Dispose(); // Dobbiamo ricordarci!
}
}
// ✅ CON using (automatico, sicuro)
try
{
using (StreamReader reader = new StreamReader("file.txt"))
{
string contenuto = reader.ReadToEnd();
Console.WriteLine(contenuto);
// reader.Dispose() viene chiamato AUTOMATICAMENTE
// anche se si verifica un'eccezione!
}
}
catch (IOException ex)
{
Console.WriteLine($"Errore: {ex.Message}");
}
// ✅ Using moderno (C# 8.0+)
try
{
using StreamReader reader = new StreamReader("file.txt");
string contenuto = reader.ReadToEnd();
Console.WriteLine(contenuto);
// Dispose chiamato automaticamente a fine scope
}
catch (IOException ex)
{
Console.WriteLine($"Errore: {ex.Message}");
}
using funziona con oggetti che implementano IDisposable.
Comuni esempi:
StreamReader/StreamWriter(file)SqlConnection/SqlCommand(database)HttpClient(HTTP requests)Graphics(disegno)
✅ Best Practices
Evita catch (Exception) generico quando possibile.
// ❌ MALE: Troppo generico
try
{
int numero = int.Parse(input);
}
catch (Exception)
{
Console.WriteLine("Errore generico"); // Poco utile!
}
// ✅ BENE: Specifico
try
{
int numero = int.Parse(input);
}
catch (FormatException)
{
Console.WriteLine("Il valore inserito non è un numero valido");
}
catch (OverflowException)
{
Console.WriteLine("Il numero è troppo grande");
}
catch (ArgumentNullException)
{
Console.WriteLine("Nessun valore inserito");
}
Le eccezioni sono costose. Usa validazione normale quando possibile.
// ❌ MALE: Usa eccezioni per controllo flusso
try
{
int numero = int.Parse(input);
}
catch (FormatException)
{
numero = 0; // Default
}
// ✅ BENE: Usa TryParse
int numero;
if (!int.TryParse(input, out numero))
{
numero = 0; // Default
}
// Più veloce, più chiaro, nessuna eccezione!
Aiuta gli utenti a capire cosa è andato storto e come risolvere.
// ❌ MALE
catch (Exception ex)
{
Console.WriteLine("Errore");
}
// ✅ BENE
catch (FileNotFoundException ex)
{
Console.WriteLine($"❌ File non trovato: {ex.FileName}");
Console.WriteLine("Assicurati che il file esista nella directory corretta.");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("❌ Permessi insufficienti per accedere al file.");
Console.WriteLine("Esegui il programma come amministratore.");
}
Registra le eccezioni per debugging e monitoraggio.
public void ProcessaDati(string percorso)
{
try
{
// Elaborazione...
}
catch (Exception ex)
{
// Log completo per debugging
LogErrore(ex);
// Messaggio user-friendly
Console.WriteLine("Si è verificato un errore durante l'elaborazione.");
// Re-throw se necessario
throw;
}
}
private void LogErrore(Exception ex)
{
string log = $"[{DateTime.Now}] ERRORE: {ex.GetType().Name}\n";
log += $"Messaggio: {ex.Message}\n";
log += $"Stack Trace: {ex.StackTrace}\n";
File.AppendAllText("errori.log", log);
}
- ✅ Cattura eccezioni specifiche, non generiche
- ✅ Usa
TryParseinvece diParse + try-catch - ✅ Usa
usingper gestione automatica risorse - ✅ Fornisci messaggi di errore chiari all’utente
- ✅ Log le eccezioni per debugging
- ❌ Non catturare eccezioni che non puoi gestire
- ❌ Non usare
catchvuoti (catch { }) - ❌ Non usare eccezioni per controllo flusso normale
🚀 Esempio Completo: Calcolatrice Robusta
using System;
class Calcolatrice
{
public double Dividi(double a, double b)
{
if (b == 0)
{
throw new DivideByZeroException("Impossibile dividere per zero");
}
return a / b;
}
public double Radice(double numero)
{
if (numero < 0)
{
throw new ArgumentException("Non si può calcolare la radice di un numero negativo");
}
return Math.Sqrt(numero);
}
public double ParseNumero(string input)
{
if (string.IsNullOrWhiteSpace(input))
{
throw new ArgumentNullException(nameof(input), "L'input non può essere vuoto");
}
if (!double.TryParse(input, out double risultato))
{
throw new FormatException($"'{input}' non è un numero valido");
}
return risultato;
}
}
class Program
{
static void Main()
{
Calcolatrice calc = new Calcolatrice();
bool continua = true;
Console.WriteLine("=== CALCOLATRICE ROBUSTA ===\n");
while (continua)
{
try
{
// Menu
Console.WriteLine("\nOperazioni:");
Console.WriteLine("1. Divisione");
Console.WriteLine("2. Radice Quadrata");
Console.WriteLine("3. Esci");
Console.Write("\nScelta: ");
string scelta = Console.ReadLine();
switch (scelta)
{
case "1":
EseguiDivisione(calc);
break;
case "2":
EseguiRadice(calc);
break;
case "3":
continua = false;
Console.WriteLine("Arrivederci!");
break;
default:
Console.WriteLine("❌ Scelta non valida!");
break;
}
}
catch (Exception ex)
{
// Catch globale per errori imprevisti
Console.WriteLine($"\n❌ ERRORE IMPREVISTO: {ex.Message}");
LogErrore(ex);
}
}
}
static void EseguiDivisione(Calcolatrice calc)
{
try
{
Console.Write("Dividendo: ");
double a = calc.ParseNumero(Console.ReadLine());
Console.Write("Divisore: ");
double b = calc.ParseNumero(Console.ReadLine());
double risultato = calc.Dividi(a, b);
Console.WriteLine($"\n✅ Risultato: {risultato:F2}");
}
catch (ArgumentNullException ex)
{
Console.WriteLine($"❌ {ex.ParamName}: {ex.Message}");
}
catch (FormatException ex)
{
Console.WriteLine($"❌ Formato non valido: {ex.Message}");
}
catch (DivideByZeroException ex)
{
Console.WriteLine($"❌ {ex.Message}");
Console.WriteLine("💡 Suggerimento: Inserisci un divisore diverso da zero");
}
}
static void EseguiRadice(Calcolatrice calc)
{
try
{
Console.Write("Numero: ");
double numero = calc.ParseNumero(Console.ReadLine());
double risultato = calc.Radice(numero);
Console.WriteLine($"\n✅ Radice quadrata: {risultato:F2}");
}
catch (ArgumentNullException ex)
{
Console.WriteLine($"❌ {ex.ParamName}: {ex.Message}");
}
catch (FormatException ex)
{
Console.WriteLine($"❌ Formato non valido: {ex.Message}");
}
catch (ArgumentException ex)
{
Console.WriteLine($"❌ {ex.Message}");
Console.WriteLine("💡 Suggerimento: Inserisci un numero positivo");
}
}
static void LogErrore(Exception ex)
{
string log = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {ex.GetType().Name}: {ex.Message}\n";
try
{
using (StreamWriter writer = File.AppendText("errori.log"))
{
writer.WriteLine(log);
}
}
catch
{
// Se anche il logging fallisce, non fare nulla
// (evita loop infiniti di errori)
}
}
}
- Try-catch stratificato: Catch specifici per ogni operazione
- Validazione preventiva: TryParse invece di Parse
- Throw custom: Eccezioni con messaggi chiari
- Using statement: Gestione automatica file log
- Messaggi user-friendly: Spiegazioni + suggerimenti
- Logging: Traccia errori per debugging
- Graceful degradation: Il programma non crasha mai
- try: codice rischioso
- catch: gestione errore
- finally: sempre eseguito
- Catch multipli specifici
- FormatException
- NullReferenceException
- ArgumentException
- Eccezioni custom
- Lancia eccezioni
- Re-throw
- Eccezioni personalizzate
- Validazione parametri
- Catch specifiche
- Using statement
- Messaggi chiari
- Logging errori
Lezione 15 del Corso C# Completo