ECCEZIONI IN C SHARP

C sharpSino adesso nei nostri programmi abbiamo dato per scontato che l’esecuzione avvenisse senza errori. Tuttavia a prescindere dalla bravura di chi scrive il codice, le situazioni di errore possono verificarsi semplicemente perché le ragioni per cui ciò avviene non sono sempre prevedibili o gestibili: un metodo può sollevare un errore a causa di una svista dello sviluppatore, ma anche in risposta a un problema hardware o al tentativo di accedere a una risorsa non disponibile. Vediamo ora il meccanismo che .NET 8 ci mette a disposizione per limitare gli errori che si possono verificare. Per analizzare le eccezioni in c sharp partiamo da un piccolo programma che effettua la divisione tra due numeri.

using System;
namespace Eccezioni
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.Write("Inserisci il Dividendo: ");
            string dividendo = Console.ReadLine();
            Console.Write("Inserisci il Divisore:");
            string divisore = Console.ReadLine();
            int num1 = Convert.ToInt32(dividendo);
            int num2 = Convert.ToInt32(divisore);
            int result = Divisione(num1,num2);
            Console.WriteLine(result);
        }
        static int Divisione(int a, int b)
        {
            return a/b;
        }
    }
}

Cosa succede se come divisore l’utente passa lo zero? E se invece di passare un numero passa una stringa? In entrambe le situazioni si verifica un’eccezione non gestita, ciò significa che il programma termina nel punto in cui si verifica l’errore e mostra nella console un messaggio di errore.

Compiler Error

I BLOCCHI TRY CATCH

Come vedi dalla figura il codice termina senza essere gestito Unhandled exception e l’eccezione sollevata è di tipo System.DivideByZeroException. Per gestire questa situazione si usa un blocco try con cui diciamo al compilatore che le istruzioni che seguono potrebbero generare un’eccezione, se l’eccezione non si verifica il flusso del programma continua normalmente. Il blocco catch (cattura) si occupa di gestire l’eventuale condizione di errore. Vediamo un esempio di codice.

using System;
namespace Eccezioni
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.Write("Inserisci il Dividendo: ");
            string dividendo = Console.ReadLine();
            Console.Write("Inserisci il Divisore:");
            string divisore = Console.ReadLine();
            int num1 = Convert.ToInt32(dividendo);
            int num2 = Convert.ToInt32(divisore);
            int result = Divisione(num1,num2);
            Console.WriteLine(result);
        }
        static int Divisione(int a, int b)
        {
            try
            {
                 return a/b;
            }
            catch(DivideByZeroException e)
            {
                Console.WriteLine($"Hai inserito lo zero come divisore, eccezione generata {e.Message}");
                return 0;
            }
            catch(Exception e)
            {
                Console.WriteLine($"Si è verificata questa eccezione: {e.Message}");
                return 0;
            }
        }
    }
}

LA CLASSE SYSTEM.EXCEPTION

Come vedi si possono inserire più blocchi catch. Il .NET 8 è una tecnologia completamente orientata verso la programmazione a oggetti e sfrutta questo paradigma anche per rappresentare gli errori che vengono sollevati a Runtime. Abbiamo accennato al fatto che, al verificarsi di un’eccezione, il CLR associa al contesto di esecuzione un’istanza di un particolare oggetto il cui tipo è (o deriva da) Exception. Proprio il tipo utilizzato è la prima discriminante che possiamo usare per determinare la natura dell’eccezione che si è verificata, come abbiamo potuto verificare nell’esempio una divisione per zero solleva infatti una DivisionByZeroException, mentre invece, accedere in lettura a un file inesistente, genera un errore rappresentato dal tipo FileNotFoundException.

GERARCHIA DELLE EXCEPTION

La classe Exception in particolare, è capostipite di una numerosa gerarchia di tipi, ognuno dei quali è associato a una particolare casistica di errore. La classe Exception rappresenta un’eccezione più generica, per questo motivo se vogliamo specificare più blocchi catch prima vanno inserite le classi di eccezione più generiche e in fondo la classe Exception. Ovviamente mettendo Exception prima delle eccezioni più specifiche queste non verrebbero mai eseguite.

Gestione Eccezioni

ECCEZIONI IN C SHARP RILANCIARE UN’ECCEZIONE LA CLAUSOLA THROW

A volte in una gerarchia di operazioni tra metodi può risultare utile, dopo aver catturato l’eccezione, rilanciarla ai chiamanti usando la keyword throw ad esempio nel codice che ti ho mostrato anziché gestire l’errore di divisione per zero nel metodo lo potremmo fare nel Main creando un gestore centralizzato che si occupi della divisione per zero e di una eventuale immissione di un valore non numerico che porterebbe ad una nuova eccezione.

using System;
namespace Eccezioni
{
    class Program
    {
        static void Main(string[] args)
        {
            int result = 0;
            Console.Write("Inserisci il Dividendo:");
            string dividendo = Console.ReadLine();
            Console.Write("Inserisci il Divisore:");
            string divisore = Console.ReadLine();
            try
            {
                int num1 = Convert.ToInt32(dividendo);
                int num2 = Convert.ToInt32(divisore);
                result = Divisione(num1,num2);
            }
            catch(DivideByZeroException e)
            {
                Console.WriteLine($"Hai inserito lo zero come divisore, eccezione generata {e.Message}");
            }
            catch(InvalidCastException e)
            {
                Console.WriteLine($"Hai inserito valori non validi eccezione generata {e.Message}");
            }
            catch(Exception e)
            {
                Console.WriteLine($"Si è verificata questa eccezione: {e.Message}");
            }
            Console.WriteLine(result);
        }
        static int Divisione(int a, int b)
        {
            try
            {
               return a/b; 
            }
            catch(Exception ex)
            {
                throw new DivideByZeroException("Divisione per zero",ex);
            }
        }
    }
}

IL BLOCCO FINALLY

Alle volte la necessità che abbiamo non è quella di intercettare una particolare tipologia d’eccezione, ma di assicurarci che una determinata porzione di codice venga eseguita comunque, sia in presenza sia in assenza di un errore. Tipicamente si tratta di codice detto di cleanup, ossia utilizzato per liberare risorse. Si pensi ad esempio ad un file che è stato aperto in lettura, o alla connessione ad un database. In questi casi è necessario liberare le risorse utilizzate il prima possibile, e ciò può essere fatto in un blocco finally.

using System;
namespace Eccezioni
{
    class Program
    {
        static void Main(string[] args)
        {
            int result = 0;
            Console.Write("Inserisci il Dividendo: ");
            string dividendo = Console.ReadLine();
            Console.Write("Inserisci il Divisore: ");
            string divisore = Console.ReadLine();
            try
            {
                int num1 = Convert.ToInt32(dividendo);
                int num2 = Convert.ToInt32(divisore);
                result = Divisione(num1,num2);
            }
            catch(DivideByZeroException e)
            {
                Console.WriteLine($"Hai inserito lo zero come divisore, eccezione generata {e.Message}");
            }
            catch(InvalidCastException e)
            {
                Console.WriteLine($"Hai inserito valori non validi eccezione generata {e.Message}");
            }
            catch(Exception e)
            {
                Console.WriteLine($"Si è verificata questa eccezione: {e.Message}");
            }
            finally
            {
                Console.WriteLine(result);
            }
           
        }
        static int Divisione(int a, int b)
        {
            try
            {
               return a/b; 
            }
            catch(Exception ex)
            {
                if(b==0)
                    throw new DivideByZeroException("Divisione per zero.",ex);
                else
                    throw new Exception("Si è verificata un'eccezione generica.",ex);   
            }
            
        }
    }
}

APPROFONDIMENTO

In C#, le eccezioni sono meccanismi per gestire gli errori e le condizioni anomale che possono verificarsi durante l’esecuzione di un programma. Utilizzando le eccezioni, è possibile separare il codice che gestisce gli errori dal codice che esegue operazioni normali. Ecco una panoramica di come funzionano le eccezioni in C# e come usarle.

Struttura base delle eccezioni

In C#, le eccezioni sono gestite utilizzando i blocchi try, catch, finally e throw.

try: Contiene il codice che può generare un’eccezione.

catch: Blocca le eccezioni sollevate nel blocco try e specifica come gestirle.

finally: Contiene il codice che viene eseguito indipendentemente dal fatto che si sia verificata un’eccezione o meno.

throw: Utilizzato per sollevare un’eccezione.

Esempio di base

try
{
       // Codice che potrebbe generare un’eccezione
       int[] numbers = { 1, 2, 3 };
Console.WriteLine(numbers[5]); // Questo causerà un’eccezione
}
catch (IndexOutOfRangeException ex)
{
       // Gestisce l’eccezione
Console.WriteLine(“Errore: Indice fuori dai limiti dell’array.“);
Console.WriteLine(ex.Message);
}
finally
{
        // Codice che viene eseguito comunque
Console.WriteLine(“Blocco finally sempre eseguito.“);
}

Tipi comuni di eccezioni

Alcuni dei tipi di eccezioni comuni in C# includono:

System.Exception: La classe base per tutte le eccezioni.

System.ArgumentException: Sollevata quando un argomento non è valido.

System.ArgumentNullException: Sollevata quando un argomento null non è valido.

System.InvalidOperationException: Sollevata quando un’operazione non è valida per lo stato attuale dell’oggetto.

System.NullReferenceException: Sollevata quando si tenta di dereferenziare un oggetto null.

System.IndexOutOfRangeException: Sollevata quando si tenta di accedere a un elemento di un array con un indice fuori dai limiti.

Sollevare eccezioni

È possibile sollevare eccezioni personalizzate utilizzando la parola chiave throw.

public void VerificaEtà(int età)
{
      if (età < 0)
{
        throw new ArgumentOutOfRangeException(nameof(età), “L’età non può essere negativa.“);
}
      // Codice per gestire età valide
}

Creare eccezioni personalizzate

Per creare una propria eccezione, è possibile ereditare dalla classe System.Exception.

public class MiaEccezionePersonalizzata : Exception
{
     public MiaEccezionePersonalizzata() { }

     public MiaEccezionePersonalizzata(string message)
     : base(message) { }

     public MiaEccezionePersonalizzata(string message, Exception inner)
     : base(message, inner) { }
}

Utilizzo delle eccezioni personalizzate

try
{
      // Codice che potrebbe sollevare una MiaEccezionePersonalizzata
}
catch (MiaEccezionePersonalizzata ex)
{
Console.WriteLine($”Errore personalizzato: {ex.Message}”);
}

Best Practices

1. Gestire solo le eccezioni che si possono gestire: Non catturare eccezioni che non si sa come gestire.

2. Utilizzare eccezioni specifiche: Catturare eccezioni specifiche anziché utilizzare catch (Exception ex).

3. Non abusare delle eccezioni: Le eccezioni devono essere utilizzate per condizioni eccezionali e non per il normale flusso di controllo.

4. Log delle eccezioni: Registrare le eccezioni per facilitare il debugging e la manutenzione del software.

Gestire correttamente le eccezioni migliora la robustezza e la manutenibilità del codice.

ECCEZIONI IN C SHARP LINK AI PRECEDENTI POST

LINK AL CODICE SU GITHUB