1. Introduzione
In un mondo ideale il codice dovrebbe essere perfetto. Ma purtroppo anche il programmatore più attento prima o poi si scontra con situazioni impreviste: file non trovato, l’utente ha inserito una stringa invece di un numero, la rete smette improvvisamente di funzionare, oppure qualcosa torna dal database in modo diverso da come ci si aspettava.
Se non intercetti questi problemi — il programma semplicemente “crasherà”, a volte con un messaggio di errore misterioso e uno stack trace. Non è quello che si aspetta l’utente o il futuro team di supporto. Bisogna imparare a “catturare” questi errori e reagire: chiudere il programma in modo pulito, mostrare un messaggio comprensibile o addirittura sistemare la situazione al volo.
try-catch — è il modo per dire al compilatore e al runtime: “Prova a eseguire questo pezzo di codice. Se succede qualcosa, non andare nel panico, dammi una possibilità di sistemare!”
2. Sintassi base di try-catch
La struttura generale è molto semplice:
try
{
// Qui scriviamo codice rischioso (o potenzialmente rischioso)
}
catch (ExceptionType variableName)
{
// Qui scriviamo cosa fare se si verifica un’eccezione di tipo ExceptionType
}
- Il blocco try — è la “zona pericolosa”. Qui mettiamo le operazioni che potrebbero “esplodere”.
- Il blocco catch — cattura eccezioni di un certo tipo. Se dentro try succede un’eccezione, il controllo passa subito al primo catch adatto, e il codice dopo il “punto problematico” in try non verrà più eseguito.
Esempio: gestione semplice di un’eccezione
Supponiamo che il nostro mini-calcolatore dalle lezioni precedenti ora sappia dividere i numeri. Ma l’utente potrebbe inserire zero! Vediamo cosa succede senza gestione degli errori:
int a = 10;
int b = 0;
int result = a / b; // BOOM! System.DivideByZeroException
Console.WriteLine(result);
Il programma terminerà con l’errore DivideByZeroException:
Unhandled exception. System.DivideByZeroException: Attempted to divide by zero.
Ora “sistemiamo” usando try-catch:
int a = 10;
int b = 0;
try
{
int result = a / b; // operazione rischiosa
Console.WriteLine("Risultato della divisione: " + result);
}
catch (DivideByZeroException ex)
{
Console.WriteLine("Ops! Divisione per zero impossibile: " + ex.Message);
}
Ora il programma non “crasherà”. In console apparirà un messaggio che la divisione per zero non è possibile:
Ops! Divisione per zero impossibile: Attempted to divide by zero.
3. Come funziona try-catch: passo dopo passo
In realtà dentro try puoi mettere quante righe di codice vuoi. Se una delle azioni dentro il blocco lancia un’eccezione, il controllo passa subito al catch più vicino e adatto. Tutto quello che sta dopo il punto dell’errore nel blocco try non verrà eseguito.
Vediamo un esempio più grande:
try
{
Console.WriteLine("Iniziamo...");
int[] numbers = { 1, 2, 3 };
Console.WriteLine(numbers[1]); // ok
Console.WriteLine(numbers[5]); // errore: indice fuori dai limiti dell’array
Console.WriteLine("Questo messaggio non apparirà mai!");
}
catch (IndexOutOfRangeException)
{
Console.WriteLine("Errore: Tentativo di accesso a un elemento inesistente dell’array");
}
L’output in console sarà:
Iniziamo...
2
Errore: Tentativo di accesso a un elemento inesistente dell’array
Process finished with exit code 0.
Come funziona questo programma?
- Viene mostrato il messaggio "Iniziamo...".
- Poi il programma prova a mostrare l’elemento numbers[1] — che è 2.
- Quando arriva a numbers[5], succede un “crash” e viene lanciata IndexOutOfRangeException.
- Il programma passa subito al blocco catch, saltando Console.WriteLine("Questo messaggio non apparirà mai!");
- In console appare il nostro messaggio dal catch.
4. Si possono gestire tipi diversi di errori in modo diverso?
Certo! Possiamo mettere più blocchi catch per reagire in modo diverso a tipi diversi di eccezioni. È comodo: per esempio, se una cosa riguarda la lettura di un file (può esserci FileNotFoundException), e un’altra la divisione (può esserci DivideByZeroException).
try
{
// Il tuo codice rischioso
}
catch (DivideByZeroException)
{
Console.WriteLine("Errore di divisione per zero");
}
catch (IndexOutOfRangeException)
{
Console.WriteLine("Errore: indice fuori dai limiti dell’array");
}
catch (Exception ex)
{
Console.WriteLine("Qualcosa di imprevisto: " + ex.Message);
}
Qui l’ultimo blocco catch con tipo Exception — è il “prendi tutto”. Cattura tutte le altre eccezioni che non sono state prese dai blocchi precedenti. Ma attenzione: se lo metti per primo, gli altri non verranno mai raggiunti! Metti sempre i catch “larghi” alla fine.
5. Com’è fatto l’oggetto eccezione?
Dentro il blocco catch puoi opzionalmente dichiarare una variabile — tipo catch (Exception ex). Dentro ci trovi tutte le info sull’errore: messaggio, tipo, stack trace, eccezioni annidate.
Ecco un piccolo esempio:
try
{
string? text = null;
Console.WriteLine(text.Length); // Ops! NullReferenceException
}
catch (NullReferenceException ex)
{
Console.WriteLine("Eccezione catturata: " + ex.Message);
Console.WriteLine("Stack trace: " + ex.StackTrace);
}
Questo approccio è una salvezza quando fai debug di errori complessi: puoi sempre vedere dove e perché il programma si è “inciampato”.
6. Catturare l’errore e continuare a lavorare
Nella realtà, soprattutto nelle app per utenti, un errore non è la fine del mondo. Se uno inserisce un percorso file sbagliato, può capitare. Non è un motivo per far crashare il programma in modo isterico. Invece chiediamo semplicemente di riprovare.
L’esempio sotto mostra un approccio semplice ma efficace: mettiamo la lettura del file in un ciclo, e se qualcosa va storto, mostriamo l’errore e chiediamo di riprovare. Niente panico, niente schermi rossi.
bool success = false;
while (!success)
{
Console.Write("Inserisci il nome del file: ");
string fileName = Console.ReadLine() ?? "";
try
{
string content = File.ReadAllText(fileName);
Console.WriteLine("File letto con successo!");
success = true; // EVVIVA!
}
catch (FileNotFoundException)
{
Console.WriteLine("Errore: file non trovato. Riprova.");
}
}
Qui l’idea chiave non è solo catturare l’eccezione, ma gestirla bene: non facciamo crashare il programma, ma lo mettiamo in modalità “riprova, ma stavolta bene”. L’utente riceve un suggerimento chiaro, il programma una seconda chance, e tu — rispetto e gratitudine.
7. Errori tipici quando si lavora con try-catch
Errore n°1: Avvolgere tutto il codice in un unico try-catch universale.
Sembra comodo — un try, un catch (Exception), e sembra che “tutto sia protetto”. Ma in realtà perdi il controllo della situazione. Non sai dove è successo il problema, né a quale errore stai davvero reagendo. Alla fine, invece di affidabilità — perdi completamente la diagnosi.
Errore n°2: Catturare eccezioni e non fare nulla.
Esempio dalla vita reale:
try
{
// qualcosa di potenzialmente rischioso
}
catch
{
// silenzio...
}
Questo codice dice letteralmente: “Qualcosa è andato storto... vabbè, chi se ne frega”. È pericoloso: il programma può continuare in uno stato sbagliato e tu nemmeno saprai che c’è stato un errore. Almeno mostra un messaggio o logga il problema. Mai lasciare
catch vuoti!
Errore n°3: Catturare un tipo di eccezione troppo generico.
Se catturi Exception, anche se sai che può succedere, per esempio, FormatException o FileNotFoundException, — ti togli la possibilità di reagire in modo adeguato al problema specifico. Più il tuo catch è preciso, più il programma si comporta in modo prevedibile e intelligente in situazioni strane.
8. Per cosa NON è consigliato usare le eccezioni
A volte i principianti iniziano a usare le eccezioni... come normale meccanismo di controllo della logica, tipo:
try
{
if (x < 0) throw new Exception("x deve essere >= 0");
}
catch (Exception)
{
// rollback dello stato, continuare a lavorare...
}
Non farlo! Le eccezioni sono uno strumento per situazioni inaspettate, rare (errore di lettura file, crash di rete ecc.), non per i normali controlli di validità dei dati utente. Per questi casi usa le istruzioni condizionali (if, else) e restituisci valori speciali se qualcosa non va.
GO TO FULL VERSION