Creare e generare eccezioni

Le eccezioni vengono usate per indicare che si è verificato un errore durante l'esecuzione del programma. Vengono creati oggetti eccezione che descrivono un errore e quindi generati con l’istruzione throw o l’espressione. Il runtime cerca quindi il gestore di eccezioni più compatibile.

I programmatori devono generare eccezioni quando una o più delle condizioni seguenti sono true:

  • Il metodo non può completare la funzionalità definita. Ad esempio, se un parametro verso un metodo ha un valore non valido:

    static void CopyObject(SampleClass original)
    {
        _ = original ?? throw new ArgumentException("Parameter cannot be null", nameof(original));
    }
    
  • Viene eseguita una chiamata non appropriata a un oggetto, in base allo stato dell'oggetto. Un esempio potrebbe essere il tentativo di scrivere su un file di sola lettura. Nei casi in cui lo stato di un oggetto non consente un'operazione, generare un'istanza di InvalidOperationException o un oggetto basato su una derivazione di questa classe. Il codice seguente è un esempio di un metodo che genera un oggetto InvalidOperationException:

    public class ProgramLog
    {
        FileStream logFile = null!;
        public void OpenLog(FileInfo fileName, FileMode mode) { }
    
        public void WriteLog()
        {
            if (!logFile.CanWrite)
            {
                throw new InvalidOperationException("Logfile cannot be read-only");
            }
            // Else write data to the log and return.
        }
    }
    
  • Quando un argomento verso un metodo genera un'eccezione. In questo caso, è necessario intercettare l'eccezione originale e creare un'istanza di ArgumentException. L'eccezione originale deve essere passata al costruttore di ArgumentException come parametro InnerException:

    static int GetValueFromArray(int[] array, int index)
    {
        try
        {
            return array[index];
        }
        catch (IndexOutOfRangeException e)
        {
            throw new ArgumentOutOfRangeException(
                "Parameter index is out of range.", e);
        }
    }
    

    Nota

    Nell'esempio precedente viene illustrato come utilizzare la proprietà InnerException. È intenzionalmente semplificato. In pratica, è necessario verificare che un indice sia compreso nell'intervallo prima di utilizzarlo. È possibile usare questa tecnica di wrapping di un'eccezione quando un membro di un parametro genera un'eccezione che non è stato possibile prevedere prima che il membro sia stato chiamato.

Le eccezioni contengono una proprietà denominata StackTrace. Questa stringa contiene il nome dei metodi nello stack di chiamate corrente, insieme al numero di riga e al nome del file in cui è stata generata l'eccezione per ogni metodo. Viene creato in automatico un oggetto StackTrace da Common Language Runtime (CLR) dal punto dell'istruzione throw, in modo tale che le eccezioni debbano essere generate dal punto in cui deve iniziare l'analisi dello stack.

Tutte le eccezioni contengono una proprietà denominata Message. Questa stringa deve essere impostata per spiegare il motivo dell'eccezione. Le informazioni sensibili alla sicurezza non devono essere inserite nel testo del messaggio. Oltre a Message, ArgumentException contiene una proprietà denominata ParamName che deve essere impostata sul nome dell'argomento che ha causato la generazione dell'eccezione. In un setter di proprietà, ParamName deve essere impostato su value.

I metodi pubblici e protetti generano eccezioni quando non sono in grado di completare le funzioni previste. La classe di eccezione generata è l'eccezione più specifica disponibile che soddisfa le condizioni di errore. Queste eccezioni devono essere documentate come parte delle funzionalità della classe e le classi derivate o gli aggiornamenti per la classe originale devono conservare lo stesso comportamento per la compatibilità con le versioni precedenti.

Aspetti da evitare nella generazione di eccezioni

L'elenco seguente include operazioni da evitare durante la generazione di eccezioni:

  • Non usare le eccezioni per modificare il flusso di un programma come parte dell'esecuzione ordinaria. Usare le eccezioni per segnalare e gestire le condizioni di errore.
  • Le eccezioni non devono essere restituite come valore restituito o parametro anziché essere generate.
  • Non generare System.Exception, System.SystemException, System.NullReferenceException o System.IndexOutOfRangeException intenzionalmente dal proprio codice sorgente.
  • Non creare eccezioni che possono essere generate in modalità di debug ma non in modalità di rilascio. Per identificare gli errori di run-time durante la fase di sviluppo, usare il metodo di asserzione di debug.

Eccezioni nei metodi di restituzione delle attività

I metodi dichiarati con il modificatore async presentano alcune considerazioni speciali quando si tratta di eccezioni. Le eccezioni generate in un metodo async vengono archiviate nell'attività restituita e non emergono finché, ad esempio, l'attività è attesa. Per altre informazioni sulle eccezioni archiviate, vedere Eccezioni asincrone.

È consigliabile convalidare gli argomenti e generare eventuali eccezioni corrispondenti, ad esempio ArgumentException e ArgumentNullException, prima di immettere le parti asincrone dei metodi. Ovvero, queste eccezioni di convalida devono emergere in modo sincrono prima dell'avvio del lavoro. Il frammento di codice seguente mostra un esempio in cui, se vengono generate eccezioni, le eccezioni ArgumentException emergerebbero in modo sincrono, mentre l'oggetto InvalidOperationException verrebbe archiviato nell'attività restituita.

// Non-async, task-returning method.
// Within this method (but outside of the local function),
// any thrown exceptions emerge synchronously.
public static Task<Toast> ToastBreadAsync(int slices, int toastTime)
{
    if (slices is < 1 or > 4)
    {
        throw new ArgumentException(
            "You must specify between 1 and 4 slices of bread.",
            nameof(slices));
    }

    if (toastTime < 1)
    {
        throw new ArgumentException(
            "Toast time is too short.", nameof(toastTime));
    }

    return ToastBreadAsyncCore(slices, toastTime);

    // Local async function.
    // Within this function, any thrown exceptions are stored in the task.
    static async Task<Toast> ToastBreadAsyncCore(int slices, int time)
    {
        for (int slice = 0; slice < slices; slice++)
        {
            Console.WriteLine("Putting a slice of bread in the toaster");
        }
        // Start toasting.
        await Task.Delay(time);

        if (time > 2_000)
        {
            throw new InvalidOperationException("The toaster is on fire!");
        }

        Console.WriteLine("Toast is ready!");

        return new Toast();
    }
}

Definire le classi di eccezioni

I programmi possono generare una classe di eccezione predefinita nello spazio dei nomi System, tranne nei casi indicati in precedenza, oppure creare le proprie classi di eccezione derivando da Exception. Le classi derivate devono definire almeno tre costruttori: un costruttore senza parametri, uno che imposta la proprietà del messaggio e uno che imposta entrambe le proprietà Message e InnerException. Ad esempio:

[Serializable]
public class InvalidDepartmentException : Exception
{
    public InvalidDepartmentException() : base() { }
    public InvalidDepartmentException(string message) : base(message) { }
    public InvalidDepartmentException(string message, Exception inner) : base(message, inner) { }
}

Aggiungere nuove proprietà alla classe di eccezioni quando i dati che forniscono sono utili per risolvere l'eccezione. Se vengono aggiunte nuove proprietà alla classe di eccezioni derivata, ToString() deve essere sottoposto a override per restituire le informazioni aggiunte.

Specifiche del linguaggio C#

Per altre informazioni, vedere Eccezioni e Istruzione throw in Specifica del linguaggio C#. La specifica del linguaggio costituisce il riferimento ufficiale principale per la sintassi e l'uso di C#.

Vedi anche