Istruzioni di gestione delle eccezioni - throw, try-catch, try-finallye try-catch-finally

Usare le throw istruzioni e try per lavorare con le eccezioni. Usare l'istruzione per generare un'eccezionethrow. Usare l'istruzionetry per rilevare e gestire le eccezioni che possono verificarsi durante l'esecuzione di un blocco di codice.

Istruzione throw

L'istruzione throw genera un'eccezione:

if (shapeAmount <= 0)
{
    throw new ArgumentOutOfRangeException(nameof(shapeAmount), "Amount of shapes must be positive.");
}

In un'istruzione throw e; il risultato dell'espressione e deve essere convertibile in modo implicito in System.Exception.

È possibile usare le classi di eccezioni predefinite, ad esempio ArgumentOutOfRangeException o InvalidOperationException. .NET fornisce anche i metodi helper per generare eccezioni in determinate condizioni: ArgumentNullException.ThrowIfNull e ArgumentException.ThrowIfNullOrEmpty. È anche possibile definire classi di eccezione personalizzate che derivano da System.Exception. Per altre informazioni, vedere Creazione e generazione di eccezioni.

All'interno di un catch blocco, è possibile usare un'istruzione throw; per generare nuovamente l'eccezione gestita dal catch blocco:

try
{
    ProcessShapes(shapeAmount);
}
catch (Exception e)
{
    LogError(e, "Shape processing failed.");
    throw;
}

Nota

throw; mantiene l'analisi dello stack originale dell'eccezione, archiviata nella Exception.StackTrace proprietà . Di fronte a questo, throw e; aggiorna la StackTrace proprietà di e.

Quando viene generata un'eccezione, Common Language Runtime (CLR) cerca il catch blocco in grado di gestire questa eccezione. Se il metodo attualmente eseguito non contiene un catch blocco di questo tipo, CLR esamina il metodo che ha chiamato il metodo corrente e così via lo stack di chiamate. Se non viene trovato alcun catch blocco, CLR termina il thread in esecuzione. Per altre informazioni, vedere la sezione Modalità di gestione delle eccezioni della specifica del linguaggio C#.

Espressione throw

È anche possibile usare throw come espressione. Questo può essere utile in diversi casi, tra cui:

  • Operatore condizionale. Nell'esempio seguente viene usata un'espressione throw per generare un'eccezione ArgumentException quando la matrice args passata è vuota:

    string first = args.Length >= 1 
        ? args[0]
        : throw new ArgumentException("Please supply at least one argument.");
    
  • L'operatore null-coalescing. Nell'esempio seguente viene usata un'espressione throw per generare un'eccezione ArgumentNullException quando la stringa da assegnare a una proprietà è null:

    public string Name
    {
        get => name;
        set => name = value ??
            throw new ArgumentNullException(paramName: nameof(value), message: "Name cannot be null");
    }
    
  • lambda o metodo con corpo di espressione. Nell'esempio seguente viene usata un'espressione throw per generare un'eccezione InvalidCastException per indicare che una conversione in un DateTime valore non è supportata:

    DateTime ToDateTime(IFormatProvider provider) =>
             throw new InvalidCastException("Conversion to a DateTime is not supported.");
    

Istruzione try

È possibile usare l'istruzione try in uno dei formati seguenti: try-catch - per gestire le eccezioni che possono verificarsi durante l'esecuzione del codice all'interno di un try blocco, try-finally per specificare il codice eseguito quando il controllo esce dal try blocco e try-catch-finally - come combinazione dei due moduli precedenti.

Istruzione try-catch

Usare l'istruzione try-catch per gestire le eccezioni che possono verificarsi durante l'esecuzione di un blocco di codice. Inserire il codice in cui può verificarsi un'eccezione all'interno di un try blocco. Usare una clausola catch per specificare il tipo di base delle eccezioni che si desidera gestire nel blocco corrispondente catch :

try
{
    var result = Process(-3, 4);
    Console.WriteLine($"Processing succeeded: {result}");
}
catch (ArgumentException e)
{
    Console.WriteLine($"Processing failed: {e.Message}");
}

È possibile specificare diverse clausole catch:

try
{
    var result = await ProcessAsync(-3, 4, cancellationToken);
    Console.WriteLine($"Processing succeeded: {result}");
}
catch (ArgumentException e)
{
    Console.WriteLine($"Processing failed: {e.Message}");
}
catch (OperationCanceledException)
{
    Console.WriteLine("Processing is cancelled.");
}

Quando si verifica un'eccezione, le clausole catch vengono esaminate nell'ordine specificato, dall'alto verso il basso. Al massimo, viene eseguito un catch solo blocco per qualsiasi eccezione generata. Come illustrato nell'esempio precedente, è anche possibile omettere la dichiarazione di una variabile di eccezione e specificare solo il tipo di eccezione in una clausola catch. Una clausola catch senza alcun tipo di eccezione specificato corrisponde ad alcuna eccezione e, se presente, deve essere l'ultima clausola catch.

Se si vuole generare nuovamente un'eccezione intercettata, usare l'istruzionethrow , come illustrato nell'esempio seguente:

try
{
    var result = Process(-3, 4);
    Console.WriteLine($"Processing succeeded: {result}");
}
catch (Exception e)
{
    LogError(e, "Processing failed.");
    throw;
}

Nota

throw; mantiene l'analisi dello stack originale dell'eccezione, archiviata nella Exception.StackTrace proprietà . Di fronte a questo, throw e; aggiorna la StackTrace proprietà di e.

Filtro when eccezioni

Insieme a un tipo di eccezione, è anche possibile specificare un filtro di eccezione che esamina ulteriormente un'eccezione e decide se il blocco corrispondente catch gestisce tale eccezione. Un filtro eccezioni è un'espressione booleana che segue la when parola chiave , come illustrato nell'esempio seguente:

try
{
    var result = Process(-3, 4);
    Console.WriteLine($"Processing succeeded: {result}");
}
catch (Exception e) when (e is ArgumentException || e is DivideByZeroException)
{
    Console.WriteLine($"Processing failed: {e.Message}");
}

Nell'esempio precedente viene utilizzato un filtro di eccezione per fornire un singolo catch blocco per gestire le eccezioni di due tipi specificati.

È possibile specificare diverse catch clausole per lo stesso tipo di eccezione se si distinguono per i filtri di eccezione. Una di queste clausole potrebbe non avere alcun filtro di eccezione. Se tale clausola esiste, deve essere l'ultima delle clausole che specificano tale tipo di eccezione.

Se una catch clausola dispone di un filtro di eccezione, può specificare il tipo di eccezione uguale o minore di un tipo di eccezione di una catch clausola visualizzata dopo di essa. Ad esempio, se è presente un filtro di eccezione, non è necessario che una catch (Exception e) clausola sia l'ultima.

Eccezioni nei metodi asincroni e iteratori

Se si verifica un'eccezione in una funzione asincrona, viene propagata al chiamante della funzione quando si attende il risultato della funzione, come illustrato nell'esempio seguente:

public static async Task Run()
{
    try
    {
        Task<int> processing = ProcessAsync(-1);
        Console.WriteLine("Launched processing.");

        int result = await processing;
        Console.WriteLine($"Result: {result}.");
    }
    catch (ArgumentException e)
    {
        Console.WriteLine($"Processing failed: {e.Message}");
    }
    // Output:
    // Launched processing.
    // Processing failed: Input must be non-negative. (Parameter 'input')
}

private static async Task<int> ProcessAsync(int input)
{
    if (input < 0)
    {
        throw new ArgumentOutOfRangeException(nameof(input), "Input must be non-negative.");
    }

    await Task.Delay(500);
    return input;
}

Se si verifica un'eccezione in un metodo iteratore, viene propagata al chiamante solo quando l'iteratore passa all'elemento successivo.

Istruzione try-finally

In un'istruzione try-finally il finally blocco viene eseguito quando il controllo lascia il try blocco. Il controllo potrebbe lasciare il try blocco in seguito a

  • esecuzione normale,
  • esecuzione di un'istruzione jump (ovvero , return, breakcontinue, o goto) o
  • propagazione di un'eccezione all'esterno del try blocco.

Nell'esempio seguente viene utilizzato il finally blocco per reimpostare lo stato di un oggetto prima che il controllo lasci il metodo :

public async Task HandleRequest(int itemId, CancellationToken ct)
{
    Busy = true;

    try
    {
        await ProcessAsync(itemId, ct);
    }
    finally
    {
        Busy = false;
    }
}

È anche possibile usare il finally blocco per pulire le risorse allocate usate nel try blocco.

Nota

Quando il tipo di una risorsa implementa l'interfaccia IDisposable o IAsyncDisposable , prendere in considerazione l'istruzioneusing . L'istruzione using garantisce che le risorse acquisite vengano eliminate quando il controllo lascia l'istruzione using . Il compilatore trasforma un'istruzione using in un'istruzione try-finally .

In quasi tutti i casi finally vengono eseguiti blocchi. Gli unici casi in cui finally i blocchi non vengono eseguiti comportano la chiusura immediata di un programma. Ad esempio, tale terminazione può verificarsi a causa della Environment.FailFast chiamata o di un'eccezione OverflowException o InvalidProgramException . La maggior parte dei sistemi operativi esegue una pulizia ragionevole delle risorse durante l'arresto e lo scaricamento del processo.

Istruzione try-catch-finally

Si usa un'istruzione try-catch-finally sia per gestire le eccezioni che possono verificarsi durante l'esecuzione del try blocco e specificare il codice che deve essere eseguito quando il controllo lascia l'istruzione try :

public async Task ProcessRequest(int itemId, CancellationToken ct)
{
    Busy = true;

    try
    {
        await ProcessAsync(itemId, ct);
    }
    catch (Exception e) when (e is not OperationCanceledException)
    {
        LogError(e, $"Failed to process request for item ID {itemId}.");
        throw;
    }
    finally
    {
        Busy = false;
    }

}

Quando un'eccezione viene gestita da un catch blocco, il finally blocco viene eseguito dopo l'esecuzione di tale catch blocco (anche se si verifica un'altra eccezione durante l'esecuzione del catch blocco). Per informazioni su catch e finally blocchi, vedere rispettivamente le sezioni Istruzione try-catch e Istruzione try-finally .

Specifiche del linguaggio C#

Per altre informazioni, vedere le sezioni seguenti delle specifiche del linguaggio C#:

Vedi anche