Istruzioni di gestione delle eccezioni: throw
, try-catch
, try-finally
e try-catch-finally
Usare le istruzioni throw
e try
per lavorare con le eccezioni. Usare l'istruzione throw
per generare un'eccezione. Usare l'istruzione try
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 seguenti per generare eccezioni in determinate condizioni: ArgumentNullException.ThrowIfNull e ArgumentException.ThrowIfNullOrEmpty. È anche possibile definire classi di eccezioni personalizzate che derivano da System.Exception. Per altre informazioni, vedere Creazione e generazione di eccezioni.
All'interno di un blocco catch
è possibile usare un'istruzione throw;
per generare nuovamente l'eccezione gestita dal blocco catch
:
try
{
ProcessShapes(shapeAmount);
}
catch (Exception e)
{
LogError(e, "Shape processing failed.");
throw;
}
Nota
throw;
mantiene l'analisi dello stack originale dell'eccezione, archiviata nella proprietà Exception.StackTrace. Al contrario, throw e;
aggiorna la proprietà StackTrace di e
.
Quando viene generata un'eccezione, Common Language Runtime(CLR) cerca il blocco catch
che può gestire questa eccezione. Se il metodo attualmente in esecuzione non contiene tale blocco catch
, CLR esamina il metodo che ha chiamato il metodo corrente e così via per tutto lo stack di chiamate. Se non viene trovato alcun blocco catch
, CLR termina il thread in esecuzione. Per altre informazioni, vedere la sezione Gestione delle eccezioni della specifica del linguaggio C#.
Espressione throw
È inoltre possibile usare throw
come espressione. Ciò potrebbe risultare utile in diversi casi, tra cui:
L'operatore condizionale. Nell'esempio seguente viene usata un'espressione
throw
per generare un'eccezione ArgumentException quando la matrice passataargs
è 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 la conversione in un valore DateTime 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 blocco try
, try-finally
: per specificare il codice eseguito quando il controllo lascia il blocco try
e try-catch-finally
: come combinazione dei due formati 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 blocco try
. Usare una clausola catch per specificare il tipo di base delle eccezioni che si desidera gestire nel blocco catch
corrispondente:
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 solo blocco catch
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 a qualsiasi eccezione e, se presente, deve essere l'ultima clausola catch.
Se si vuole generare nuovamente un'eccezione rilevata, usare l'istruzione throw
, 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 proprietà Exception.StackTrace. Al contrario, throw e;
aggiorna la proprietà StackTrace di e
.
Filtro eccezioni when
Oltre a un tipo di eccezione, è anche possibile specificare un filtro eccezioni che esamina ulteriormente un'eccezione e decide se il blocco catch
corrispondente gestisce tale eccezione. Un filtro eccezioni è un'espressione booleana che segue la parola chiave when
, 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 usato un filtro eccezioni per fornire un singolo blocco catch
per gestire le eccezioni di due tipi specificati.
È possibile specificare diverse clausole catch
per lo stesso tipo di eccezione se si distinguono in base ai filtri eccezioni. Una di queste clausole potrebbe non includere alcun filtro eccezioni. Se tale clausola esiste, deve essere l'ultima delle clausole che specificano tale tipo di eccezione.
Se una clausola catch
ha un filtro eccezioni, può specificare il tipo di eccezione uguale o minore di un tipo di eccezione di una clausola catch
visualizzata dopo di essa. Ad esempio, se è presente un filtro eccezioni, non è necessario che una clausola catch (Exception e)
sia l'ultima clausola.
Eccezioni nei metodi asincroni e iteratori
Se si verifica un'eccezione in una funzione asincrona, viene propagata al chiamante della funzione durante l'attesa del 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 avanza all'elemento successivo.
Istruzione try-finally
In un'istruzione try-finally
il blocco finally
viene eseguito quando il controllo esce dal blocco try
. Il controllo potrebbe lasciare il blocco try
in seguito a
- esecuzione normale,
- esecuzione di un'istruzione jump (ovvero
return
,break
,continue
ogoto
) o - propagazione di un'eccezione all'esterno del blocco
try
.
Nell'esempio seguente viene utilizzato il blocco finally
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 blocco finally
per pulire le risorse allocate usate nel blocco try
.
Nota
Quando il tipo di una risorsa implementa l'interfaccia IDisposable o IAsyncDisposable, valutare l'utilizzo dell'istruzione using
. 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
.
L'esecuzione del blocco finally
varia a seconda che il sistema operativo scelga di generare un'operazione di rimozione dell'eccezione o meno. Gli unici casi in cui i blocchi finally
non vengono eseguiti comportano la chiusura immediata di un programma. Ad esempio, tale terminazione potrebbe verificarsi a causa della chiamata Environment.FailFast 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
per gestire le eccezioni che possono verificarsi durante l'esecuzione del blocco try
e per 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 blocco catch
, il blocco finally
viene eseguito dopo l'esecuzione di tale blocco catch
(anche se si verifica un'altra eccezione durante l'esecuzione del blocco catch
). Per informazioni sui blocchi catch
e finally
, 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#: