Vai al contenuto

Gestione delle eccezioni

Rendere i programmi robusti: try, catch e finally, i tipi di eccezione più comuni, propagazione, throw ed eccezioni personalizzate, using e buone pratiche.

🖥️ In pratica con Windows Forms: Decisioni: if, else, switch e input sicuro e Parco Auto 2: salvare e caricare i dati. Qui gli esempi sono in console.

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.

Esempio: Cosa Succede SENZA Gestione Eccezioni

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

Esempio: CON Gestione Eccezioni

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!

✨ Vantaggi della Gestione Eccezioni

Robustezza

Il programma non crasha per errori comuni

User Experience

Messaggi chiari invece di crash misteriosi

Debugging

Informazioni dettagliate sull'errore

Professionalità

Software di qualità enterprise

Try-Catch-Finally

La struttura try-catch-finally è il meccanismo principale per gestire le eccezioni in C#.

  1. Try-Catch Base

Il blocco try contiene il codice rischioso, catch gestisce l'errore.

Sintassi Base

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...
  1. Catch Multipli

Gestisci diversi tipi di eccezioni in modo specifico.

Catch Multipli (dal più specifico al più generico)

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}");
}
  1. Finally - Sempre Eseguito

Il blocco finally viene SEMPRE eseguito, anche se c'è un'eccezione.

Try-Catch-Finally

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");

📌 Ordine di Esecuzione Try-Catch-Finally

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

💡 Quando Usare Finally

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

Esempi Pratici Eccezioni Comuni

// ===== 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
}

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.

Esempio di Propagazione

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!

🔄 Flusso di Propagazione

  1. Main() chiama MetodoA()
  2. MetodoA() chiama MetodoB()
  3. MetodoB() genera un'eccezione con throw
  4. L'eccezione risale a MetodoA() (non gestita)
  5. L'eccezione risale a Main() dove viene catturata

Lanciare Eccezioni (throw)

Puoi lanciare (throw) eccezioni manualmente per segnalare errori nel tuo codice.

  1. Throw Base

Usa throw per lanciare un'eccezione.

Throw Exception

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!
}
  1. Re-throw

Puoi catturare un'eccezione, fare qualcosa, e ri-lanciarla.

Re-throw Exception

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");
}
  1. Eccezioni Custom

Crea le tue eccezioni personalizzate per situazioni specifiche.

Eccezione Personalizzata

// 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 ContoBancario(decimal saldoIniziale)
    {
        Saldo = saldoIniziale;
    }

    public void Preleva(decimal importo)
    {
        if (importo > Saldo)
        {
            throw new SaldoInsufficienteException(Saldo, importo);
        }

        Saldo -= importo;
    }
}

// Test
try
{
    ContoBancario conto = new ContoBancario(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.

Using Statement - Gestione Automatica Risorse

// ❌ 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}");
}

💡 IDisposable

using funziona con oggetti che implementano IDisposable. Comuni esempi:

  • StreamReader / StreamWriter (file)
  • SqlConnection / SqlCommand (database)
  • HttpClient (HTTP requests)
  • Graphics (disegno)

Best Practices

  1. Cattura Eccezioni Specifiche

Evita catch (Exception) generico quando possibile.

Best Practice: Specifico → Generico

// ❌ 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");
}
  1. Non Usare Eccezioni per Controllo Flusso

Le eccezioni sono costose. Usa validazione normale quando possibile.

Anti-pattern vs Best Practice

// ❌ 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!
  1. Messaggi di Errore Chiari

Aiuta gli utenti a capire cosa è andato storto e come risolvere.

Messaggi Utili

// ❌ 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.");
}
  1. Log delle Eccezioni

Registra le eccezioni per debugging e monitoraggio.

Logging Eccezioni

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);
}

🎯 Riepilogo Best Practices

  • ✅ Cattura eccezioni specifiche, non generiche
  • ✅ Usa TryParse invece di Parse + try-catch
  • ✅ Usa using per gestione automatica risorse
  • ✅ Fornisci messaggi di errore chiari all'utente
  • ✅ Log le eccezioni per debugging
  • ❌ Non catturare eccezioni che non puoi gestire
  • ❌ Non usare catch vuoti (catch { })
  • ❌ Non usare eccezioni per controllo flusso normale

Esempio Completo: Calcolatrice Robusta

Program.cs - Calcolatrice con Gestione Eccezioni

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)
        }
    }
}

💡 Analisi Gestione Eccezioni

  • 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

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
Mostra la soluzione

Soluzione Esercizio 1

using System;

class Program
{
    static void Main()
    {
        bool continua = true;

        while (continua)
        {
            Console.WriteLine("\n=== CALCOLATRICE ===");

            double num1 = LeggiNumero("Inserisci il primo numero: ");
            double num2 = LeggiNumero("Inserisci il secondo numero: ");

            Console.Write("Operazione (+, -, *, /): ");
            string operazione = Console.ReadLine();

            try
            {
                double risultato = Calcola(num1, num2, operazione);
                Console.WriteLine($"\n✅ Risultato: {num1} {operazione} {num2} = {risultato}");
            }
            catch (DivideByZeroException)
            {
                Console.WriteLine("❌ Errore: impossibile dividere per zero");
            }
            catch (ArgumentException ex)
            {
                Console.WriteLine($"❌ Errore: {ex.Message}");
            }

            Console.Write("\nVuoi continuare? (s/n): ");
            continua = Console.ReadLine().ToLower() == "s";
        }
    }

    static double LeggiNumero(string messaggio)
    {
        double numero;
        bool valido = false;

        do
        {
            Console.Write(messaggio);
            string input = Console.ReadLine();

            if (double.TryParse(input, out numero))
            {
                valido = true;
            }
            else
            {
                Console.WriteLine("❌ Input non valido. Riprova.");
            }
        } while (!valido);

        return numero;
    }

    static double Calcola(double a, double b, string op)
    {
        switch (op)
        {
            case "+": return a + b;
            case "-": return a - b;
            case "*": return a * b;
            case "/":
                if (b == 0)
                    throw new DivideByZeroException();
                return a / b;
            default:
                throw new ArgumentException("Operazione non valida");
        }
    }
}

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
Mostra la soluzione

Soluzione Esercizio 2

using System;
using System.Linq;

// Eccezioni personalizzate
class PasswordTroppoCorta : Exception
{
    public PasswordTroppoCorta(string msg) : base(msg) { }
}

class PasswordSenzaMaiuscole : Exception
{
    public PasswordSenzaMaiuscole(string msg) : base(msg) { }
}

class PasswordSenzaMinuscole : Exception
{
    public PasswordSenzaMinuscole(string msg) : base(msg) { }
}

class PasswordSenzaNumeri : Exception
{
    public PasswordSenzaNumeri(string msg) : base(msg) { }
}

class Program
{
    static void Main()
    {
        bool passwordValida = false;

        while (!passwordValida)
        {
            Console.Write("\nInserisci una password: ");
            string password = Console.ReadLine();

            try
            {
                ValidaPassword(password);
                passwordValida = true;
                Console.WriteLine("✅ Password accettata!");
            }
            catch (PasswordTroppoCorta ex)
            {
                Console.WriteLine($"❌ {ex.Message}");
            }
            catch (PasswordSenzaMaiuscole ex)
            {
                Console.WriteLine($"❌ {ex.Message}");
            }
            catch (PasswordSenzaMinuscole ex)
            {
                Console.WriteLine($"❌ {ex.Message}");
            }
            catch (PasswordSenzaNumeri ex)
            {
                Console.WriteLine($"❌ {ex.Message}");
            }
        }
    }

    static void ValidaPassword(string password)
    {
        if (password.Length < 8)
        {
            throw new PasswordTroppoCorta(
                "La password deve essere di almeno 8 caratteri");
        }

        if (!password.Any(char.IsUpper))
        {
            throw new PasswordSenzaMaiuscole(
                "La password deve contenere almeno una lettera maiuscola");
        }

        if (!password.Any(char.IsLower))
        {
            throw new PasswordSenzaMinuscole(
                "La password deve contenere almeno una lettera minuscola");
        }

        if (!password.Any(char.IsDigit))
        {
            throw new PasswordSenzaNumeri(
                "La password deve contenere almeno un numero");
        }
    }
}

Riepilogo

StrumentoA cosa serve
try / catchesegue il codice rischioso e gestisce l'errore senza far fermare il programma; più catch dal più specifico al più generico
finallycodice eseguito sempre, con o senza errore (chiudere file e connessioni)
propagazioneun'eccezione non gestita risale al metodo chiamante
throw / throw;lancia un'eccezione / la rilancia dopo averla registrata
eccezioni personalizzateclassi che ereditano da Exception per errori specifici del programma
usingchiude da solo file e risorse, anche in caso di errore
TryParseper l'input dell'utente: meglio controllare prima che catturare dopo