Condividi tramite


Creare e lanciare eccezioni

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

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

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

    static void CopyObject(SampleClass original)
    {
        _ = original ?? throw new ArgumentException("Parameter cannot be null", nameof(original));
    }
    
  • Viene effettuata una chiamata inappropriata a un oggetto, in base allo stato dell'oggetto. Un esempio potrebbe essere il tentativo di scrivere in un file di sola lettura. Nei casi in cui uno stato dell'oggetto non consente un'operazione, generare un'istanza di InvalidOperationException o un oggetto in base a una derivazione di questa classe. Il codice seguente è un esempio di 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 di un metodo causa un'eccezione. In questo caso, l'eccezione originale deve essere intercettata e deve essere creata un'istanza di ArgumentException. L'eccezione originale deve essere passata al costruttore del 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 usarlo. È possibile utilizzare questa tecnica di gestione di un'eccezione quando un componente di un parametro genera un'eccezione che non si poteva prevedere prima di chiamare il componente.

Le eccezioni contengono una proprietà denominata StackTrace. Questa stringa contiene il nome dei metodi nello stack di chiamate corrente, insieme al nome file e al numero di riga in cui è stata generata l'eccezione per ogni metodo. Un oggetto StackTrace viene creato automaticamente da Common Language Runtime (CLR) dal punto dell'istruzione throw, in modo che le eccezioni vengano 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 il sollevamento dell'eccezione. In un setter di proprietà ParamName deve essere impostato su value.

I metodi pubblici e protetti generano eccezioni ogni volta che non possono 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 della funzionalità della classe e le classi derivate o gli aggiornamenti alla classe originale devono mantenere lo stesso comportamento per la compatibilità con le versioni precedenti.

Aspetti da evitare quando si generano eccezioni

L'elenco seguente identifica le procedure da evitare quando si generano eccezioni:

  • Non usare le eccezioni per modificare il flusso di un programma come parte dell'esecuzione normale. Usare le eccezioni per segnalare e gestire le condizioni di errore.
  • Le eccezioni non devono essere restituite come valore di ritorno o parametro anziché essere lanciate.
  • Non lanciare System.Exception, System.SystemException, System.NullReferenceExceptiono System.IndexOutOfRangeException intenzionalmente dal tuo codice sorgente.
  • Non creare eccezioni che possono essere generate in modalità di debug ma non in modalità di rilascio. Per identificare gli errori di runtime durante la fase di sviluppo, usare invece Debug Assert.

Eccezioni nei metodi di restituzione delle attività

I metodi dichiarati con il modificatore async hanno alcune considerazioni speciali quando si tratta di eccezioni. Le eccezioni generate in un metodo async vengono archiviate nel task restituito e non emergono finché, ad esempio, il task non viene atteso. 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 le eccezioni, le eccezioni ArgumentException emergerebbero in modo sincrono, mentre il 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 dove indicato in precedenza) o creare classi di eccezione personalizzate 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 le proprietà Message e InnerException. Per 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 eccezione quando i dati forniti sono utili per risolvere l'eccezione. Se vengono aggiunte nuove proprietà alla classe di eccezione derivata, ToString() deve essere sottoposto a override per restituire le informazioni aggiunte.

Specifica del linguaggio C#

Per altre informazioni, vedere Eccezioni e L'istruzione throw nella specifica del linguaggio C# . La specifica del linguaggio è l'origine definitiva per la sintassi e l'utilizzo di C#.

Vedere anche