Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Istruzioni di gestione delle eccezioni:
Usare le throw istruzioni 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.
Il riferimento al linguaggio C# documenta la versione rilasciata più di recente del linguaggio C#. Contiene anche la documentazione iniziale per le funzionalità nelle anteprime pubbliche per la versione futura del linguaggio.
La documentazione identifica tutte le funzionalità introdotte nelle ultime tre versioni della lingua o nelle anteprime pubbliche correnti.
Suggerimento
Per trovare quando una funzionalità è stata introdotta per la prima volta in C#, vedere l'articolo sulla cronologia delle versioni del linguaggio C#.
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 catch blocco, 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 proprietà Exception.StackTrace. Al contrario, throw e; aggiorna la StackTrace proprietà 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 è presente alcun blocco compatibile 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. Questo approccio può essere utile in diversi casi, tra cui:
L'operatore condizionale. Nell'esempio seguente viene usata un'espressione
throwper 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
throwper 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. L'esempio seguente usa un'espressione
throwper generare un InvalidCastException oggetto 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 utilizzare 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 lascia il try blocco e try-catch-finally - come combinazione delle due forme 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, il runtime controlla le clausole catch 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 a qualsiasi eccezione e, se presente, deve essere l'ultima clausola catch.
Per generare nuovamente un'eccezione rilevata, 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 proprietà Exception.StackTrace. Al contrario, throw e; aggiorna la StackTrace proprietà 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.
Filtri eccezioni e gestione delle eccezioni tradizionali
I filtri eccezioni offrono vantaggi significativi rispetto agli approcci tradizionali di gestione delle eccezioni. La differenza principale è quando viene valutata la logica di gestione delle eccezioni:
-
Filtri eccezioni (
when): l'espressione di filtro viene valutata prima che lo stack venga sbloccato. Ciò significa che lo stack di chiamate originale e tutte le variabili locali rimangono intatte durante la valutazione del filtro. -
Blocchi tradizionali
catch: il blocco catch viene eseguito dopo l'annullamento dello stack, potenzialmente perdendo informazioni di debug preziose.
Ecco un confronto che mostra la differenza:
public static void DemonstrateStackUnwindingDifference()
{
var localVariable = "Important debugging info";
try
{
ProcessWithExceptionFilter(localVariable);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("filter"))
{
// Exception filter: Stack not unwound yet.
// localVariable is still accessible in debugger.
// Call stack shows original throwing location.
Console.WriteLine($"Caught with filter: {ex.Message}");
Console.WriteLine($"Local variable accessible: {localVariable}");
}
try
{
ProcessWithTraditionalCatch(localVariable);
}
catch (InvalidOperationException ex)
{
// Traditional catch: Stack already unwound.
// Some debugging information may be lost.
if (ex.Message.Contains("traditional"))
{
Console.WriteLine($"Caught with if: {ex.Message}");
Console.WriteLine($"Local variable accessible: {localVariable}");
}
else
{
throw; // Re-throws and further modifies stack trace.
}
}
}
private static void ProcessWithExceptionFilter(string context)
{
throw new InvalidOperationException($"Exception for filter demo: {context}");
}
private static void ProcessWithTraditionalCatch(string context)
{
throw new InvalidOperationException($"Exception for traditional demo: {context}");
}
Vantaggi dei filtri eccezioni
- Migliore esperienza di debug: poiché lo stack non viene sbloccato fino a quando un filtro non corrisponde, i debugger possono mostrare il punto di errore originale con tutte le variabili locali intatte.
- Vantaggi delle prestazioni: se nessun filtro corrisponde, l'eccezione continua a propagarsi senza sovraccarico di rimozione e ripristino dello stack.
- Codice più pulito: più filtri possono gestire condizioni diverse dello stesso tipo di eccezione senza richiedere istruzioni if-else annidate.
- Registrazione e diagnostica: è possibile esaminare e registrare i dettagli delle eccezioni prima di decidere se gestire l'eccezione:
public static void DemonstrateDebuggingAdvantage()
{
var contextData = new Dictionary<string, object>
{
["RequestId"] = Guid.NewGuid(),
["UserId"] = "user123",
["Timestamp"] = DateTime.Now
};
try
{
// Simulate a deep call stack.
Level1Method(contextData);
}
catch (Exception ex) when (LogAndFilter(ex, contextData))
{
// This catch block may never execute if LogAndFilter returns false.
// But LogAndFilter can examine the exception while the stack is intact.
Console.WriteLine("Exception handled after logging");
}
}
private static void Level1Method(Dictionary<string, object> context)
{
Level2Method(context);
}
private static void Level2Method(Dictionary<string, object> context)
{
Level3Method(context);
}
private static void Level3Method(Dictionary<string, object> context)
{
throw new InvalidOperationException("Error in deep call stack");
}
private static bool LogAndFilter(Exception ex, Dictionary<string, object> context)
{
// This method runs before stack unwinding.
// Full call stack and local variables are still available.
Console.WriteLine($"Exception occurred: {ex.Message}");
Console.WriteLine($"Request ID: {context["RequestId"]}");
Console.WriteLine($"Full stack trace preserved: {ex.StackTrace}");
// Return true to handle the exception, false to continue search.
return ex.Message.Contains("deep call stack");
}
Quando usare i filtri delle eccezioni
Usare filtri eccezioni quando è necessario:
- Gestire le eccezioni in base a condizioni o proprietà specifiche.
- Mantenere lo stack di chiamate originale per il debug.
- Registrare o esaminare le eccezioni prima di decidere se gestirle.
- Gestire lo stesso tipo di eccezione in modo diverso in base al contesto.
public static void HandleFileOperations(string filePath)
{
try
{
// Simulate file operation that might fail.
ProcessFile(filePath);
}
catch (IOException ex) when (ex.Message.Contains("access denied"))
{
Console.WriteLine("File access denied. Check permissions.");
}
catch (IOException ex) when (ex.Message.Contains("not found"))
{
Console.WriteLine("File not found. Verify the path.");
}
catch (IOException ex) when (IsNetworkPath(filePath))
{
Console.WriteLine($"Network file operation failed: {ex.Message}");
}
catch (IOException)
{
Console.WriteLine("Other I/O error occurred.");
}
}
private static void ProcessFile(string filePath)
{
// Simulate different types of file exceptions.
if (filePath.Contains("denied"))
throw new IOException("File access denied");
if (filePath.Contains("missing"))
throw new IOException("File not found");
if (IsNetworkPath(filePath))
throw new IOException("Network timeout occurred");
}
private static bool IsNetworkPath(string path)
{
return path.StartsWith(@"\\") || path.StartsWith("http");
}
Conservazione dell'analisi dello stack
I filtri eccezioni mantengono la proprietà originale ex.StackTrace . Se una catch clausola non è in grado di elaborare l'eccezione e la genera nuovamente, le informazioni sullo stack originali andranno perse. Il when filtro non rimuove lo stack, quindi se un when filtro è false, l'analisi dello stack originale non viene modificata.
L'approccio al filtro delle eccezioni è utile nelle applicazioni in cui il mantenimento delle informazioni di debug è fondamentale per la diagnosi degli errori del codice.
Eccezioni nei metodi asincroni e iteratori
Se si verifica un'eccezione in una funzione asincrona, l'eccezione 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, l'eccezione viene propagata al chiamante solo quando l'iteratore passa all'elemento successivo.
Istruzione try-finally
In un'istruzione try-finally il blocco viene eseguito quando il finally controllo esce dal try blocco. Il controllo potrebbe lasciare il blocco try in seguito a
- esecuzione normale,
- esecuzione di un'istruzione jump (ovvero
return,break,continueogoto) 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'eventuale esecuzione del finally blocco dipende dal fatto che il sistema operativo scelga di attivare un'operazione di rimozione delle eccezioni. Gli unici casi in cui finally i blocchi 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
Usare un'istruzione try-catch-finally per gestire le eccezioni che possono verificarsi durante l'esecuzione del try blocco 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 catch blocco gestisce un'eccezione, il finally blocco viene eseguito al termine del catch blocco (anche se si verifica un'altra eccezione durante l'esecuzione del catch blocco). 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#: