Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Ausnahmebehandlungsanweisungen:
Die Anweisungen throw
und try
werden für die Arbeit mit Ausnahmen verwendet. Die Anweisung throw
wird zum Auslösen einer Ausnahme verwendet. Die Anweisung try
wird verwendet, um Ausnahmen abzufangen und zu behandeln, die während der Ausführung eines Codeblocks auftreten können.
Die Anweisung throw
Die Anweisung throw
löst eine Ausnahme aus:
if (shapeAmount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(shapeAmount), "Amount of shapes must be positive.");
}
In einer throw e;
-Anweisung muss das Ergebnis des Ausdrucks e
implizit in System.Exception konvertierbar sein.
Sie können integrierten Ausnahmeklassen wie ArgumentOutOfRangeException oder InvalidOperationException verwenden. .NET bietet außerdem die folgenden Hilfsmethoden zum Auslösen von Ausnahmen unter bestimmten Bedingungen: ArgumentNullException.ThrowIfNull und ArgumentException.ThrowIfNullOrEmpty. Sie können auch eigene Ausnahmeklassen definieren, die von System.Exception abgeleitet werden. Weitere Informationen finden Sie unter Erstellen und Auslösen von Ausnahmen.
In einem catch
-Block können Sie eine throw;
-Anweisung verwenden, um die Ausnahme erneut auszulösen, die vom catch
-Block behandelt wird:
try
{
ProcessShapes(shapeAmount);
}
catch (Exception e)
{
LogError(e, "Shape processing failed.");
throw;
}
Hinweis
throw;
behält die ursprüngliche Stapelüberwachung der Ausnahme bei, die in der Exception.StackTrace-Eigenschaft gespeichert ist. Im Gegensatz dazu aktualisiert throw e;
die StackTrace-Eigenschaft von e
.
Wenn eine Ausnahme ausgelöst wird, sucht die Common Language Runtime (CLR) nach dem catch
-Block, der diese Ausnahme behandeln kann. Wenn die derzeit ausgeführte Methode keinen solchen catch
-Block enthält, berücksichtigt die CLR die Methode, die die aktuelle Methode aufgerufen hat, dann die vorhergehende in der Aufrufliste usw. Wenn kein catch
-Block gefunden wird, beendet die CLR den ausgeführten Thread. Weitere Informationen finden Sie im Abschnitt Behandlung von Ausnahmen der C#-Sprachspezifikation.
Der throw
-Ausdruck
Sie können auch throw
als Ausdruck verwenden. Dies kann bei verschiedenen Fällen wie den folgenden hilfreich sein:
Der bedingte Operator. Im folgenden Beispiel wird ein
throw
-Ausdruck verwendet, um eine ArgumentException auszulösen, wenn das übergebeneargs
-Array leer ist:string first = args.Length >= 1 ? args[0] : throw new ArgumentException("Please supply at least one argument.");
Der NULL-Sammeloperator. Im folgenden Beispiel wird ein
throw
-Ausdruck verwendet, um eine ArgumentNullException auszulösen, wenn die Zeichenfolge, die einer Eigenschaft zugewiesen werden soll,null
ist:public string Name { get => name; set => name = value ?? throw new ArgumentNullException(paramName: nameof(value), message: "Name cannot be null"); }
Ein Ausdruckskörperlambda oder eine Ausdruckskörpermethode. Im folgenden Beispiel wird ein
throw
-Ausdruck verwendet, um eine InvalidCastException auszulösen, um anzugeben, dass eine Konvertierung in einen DateTime-Wert nicht unterstützt wird:DateTime ToDateTime(IFormatProvider provider) => throw new InvalidCastException("Conversion to a DateTime is not supported.");
Die Anweisung try
Sie können die try
-Anweisung in einer der folgenden Formen verwenden: try-catch
, um Ausnahmen zu behandeln, die bei der Ausführung des Codes in einem try
-Block auftreten können, try-finally
, um den Code anzugeben, der ausgeführt wird, wenn die Steuerung den try
-Block verlässt, und try-catch-finally
als Kombination der beiden vorherigen Formate.
Die Anweisung try-catch
Die Anweisung try-catch
wird verwendet, um Ausnahmen zu behandeln, die während der Ausführung eines Codeblocks auftreten können. Fügen Sie den Code, bei dem eine Ausnahme auftreten kann, in einen try
-Block ein. Verwenden Sie eine Catch-Klausel, um den Basistyp der Ausnahmen anzugeben, die im entsprechenden catch
-Block behandelt werden sollen:
try
{
var result = Process(-3, 4);
Console.WriteLine($"Processing succeeded: {result}");
}
catch (ArgumentException e)
{
Console.WriteLine($"Processing failed: {e.Message}");
}
Sie können mehrere Catch-Klauseln angeben:
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.");
}
Wenn eine Ausnahme auftritt, werden Catch-Klauseln in der angegebenen Reihenfolge von oben nach unten untersucht. Für jede ausgelöste Ausnahme wird maximal nur ein catch
-Block ausgeführt. Wie im vorherigen Beispiel gezeigt, können Sie die Deklaration einer Ausnahmevariable weglassen und nur den Ausnahmetyp in einer Catch-Klausel angeben. Eine Catch-Klausel ohne angegebenen Ausnahmetyp entspricht jeder beliebigen Ausnahme und muss, falls vorhanden, die letzte Catch-Klausel sein.
Wenn eine abgefangene Ausnahme erneut ausgelöst werden soll, verwenden Sie die Anweisung throw
wie im folgenden Beispiel:
try
{
var result = Process(-3, 4);
Console.WriteLine($"Processing succeeded: {result}");
}
catch (Exception e)
{
LogError(e, "Processing failed.");
throw;
}
Hinweis
throw;
behält die ursprüngliche Stapelüberwachung der Ausnahme bei, die in der Exception.StackTrace-Eigenschaft gespeichert ist. Im Gegensatz dazu aktualisiert throw e;
die StackTrace-Eigenschaft von e
.
Ein when
-Ausnahmefilter
Zusammen mit einem Ausnahmetyp können Sie auch einen Ausnahmefilter angeben, der eine Ausnahme weiter untersucht und entscheidet, ob der entsprechende catch
-Block diese Ausnahme behandelt. Ein Ausnahmefilter ist ein boolescher Ausdruck, der wie im folgenden Beispiel dem Schlüsselwort when
folgt:
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}");
}
Im vorherigen Beispiel wird ein Ausnahmefilter verwendet, um einen einzelnen catch
-Block anzugeben, mit dem Ausnahmen von zwei angegebenen Typen behandelt werden.
Sie können für einen Ausnahmetyp mehrere catch
-Klauseln angeben, wenn sie sich durch Ausnahmefilter voneinander unterscheiden. Eine dieser Klauseln weist möglicherweise keinen Ausnahmefilter auf. Bei solch einer Klausel muss diese die letzte Klausel sein, die diesen Ausnahmetyp angibt.
Wenn eine catch
-Klausel einen Ausnahmefilter enthält, kann sie den Ausnahmetyp angeben, der gleich oder weniger abgeleitet ist als ein Ausnahmetyp einer catch
-Klausel, die danach angezeigt wird. Wenn beispielsweise ein Ausnahmefilter vorhanden ist, muss eine catch (Exception e)
-Klausel nicht die letzte Klausel sein.
Ausnahmefilter im Vergleich zur herkömmlichen Ausnahmebehandlung
Ausnahmefilter bieten gegenüber herkömmlichen Ausnahmebehandlungsansätzen erhebliche Vorteile. Der Hauptunterschied besteht darin, dass die Ausnahmebehandlungslogik ausgewertet wird:
-
Ausnahmefilter (
when
): Der Filterausdruck wird ausgewertet , bevor der Stapel entladen wird. Dies bedeutet, dass der ursprüngliche Aufrufstapel und alle lokalen Variablen während der Filterauswertung intakt bleiben. -
Herkömmliche
catch
Blöcke: Der Catch-Block wird ausgeführt, nachdem der Stapel entwundet wurde und potenziell wertvolle Debuginformationen verloren gehen.
Hier ist ein Vergleich, der den Unterschied zeigt:
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}");
}
Vorteile von Ausnahmefiltern
- Bessere Debugerfahrung: Da der Stapel erst nach übereinstimmungen eines Filters aufgewockt wird, können Debugger den ursprünglichen Fehlerpunkt mit allen lokalen Variablen anzeigen.
- Leistungsvorteile: Wenn keine Filterübereinstimmungen auftreten, wird die Ausnahme weiterhin weitergegeben, ohne dass der Aufwand für die Stapelaussetzung und Wiederherstellung besteht.
- Übersichtlicherer Code: Mehrere Filter können unterschiedliche Bedingungen desselben Ausnahmetyps verarbeiten, ohne dass verschachtelte if-else-Anweisungen erforderlich sind.
- Protokollierung und Diagnose: Sie können Ausnahmedetails untersuchen und protokollieren, bevor Sie entscheiden, ob die Ausnahme behandelt werden soll:
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");
}
Verwendung von Ausnahmefiltern
Verwenden Sie Ausnahmefilter, wenn Sie Folgendes benötigen:
- Behandeln sie Ausnahmen basierend auf bestimmten Bedingungen oder Eigenschaften.
- Bewahren Sie den ursprünglichen Aufrufstapel für das Debuggen auf.
- Protokollieren oder untersuchen Sie Ausnahmen, bevor Sie entscheiden, ob sie behandelt werden sollen.
- Behandeln Sie denselben Ausnahmetyp je nach Kontext unterschiedlich.
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");
}
Erhaltung der Stapelablaufverfolgung
Ausnahmefilter behalten die ursprüngliche ex.StackTrace
Eigenschaft bei. Wenn eine catch
Klausel die Ausnahme nicht verarbeiten und erneut ausgelöst werden kann, gehen die ursprünglichen Stapelinformationen verloren. Der when
Filter entspannt den Stapel nicht. Wenn ein when
Filter lautet false
, wird die ursprüngliche Stapelablaufverfolgung also nicht geändert.
Der Ausnahmefilteransatz ist in Anwendungen hilfreich, bei denen das Beibehalten von Debuginformationen für die Diagnose von Problemen von entscheidender Bedeutung ist.
Ausnahmen in asynchronen und Iteratormethoden
Wenn eine Ausnahme in einer asynchronen Funktion auftritt, wird sie wie im folgenden Beispiel an den Aufrufer der Funktion weitergegeben, wenn Sie das Ergebnis der Funktion abwarten:
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;
}
Wenn eine Ausnahme in einer Iteratormethode auftritt, wird sie nur an den Aufrufer weitergegeben, wenn der Iterator zum nächsten Element wechselt.
Die Anweisung try-finally
In einer try-finally
-Anweisung wird der finally
-Block ausgeführt, wenn die Steuerung den try
-Block verlässt. Die Steuerung kann den try
-Block aus folgenden Gründen verlassen:
- Normale Ausführung
- Ausführung einer Jump-Anweisung (d. h.,
return
,break
,continue
odergoto
) - Weitergabe einer Ausnahme aus dem
try
-Block
Im folgenden Beispiel wird der finally
-Block verwendet, um den Status eines Objekts zurückzusetzen, bevor die Steuerung die Methode verlässt:
public async Task HandleRequest(int itemId, CancellationToken ct)
{
Busy = true;
try
{
await ProcessAsync(itemId, ct);
}
finally
{
Busy = false;
}
}
Sie können auch den finally
-Block verwenden, um belegte Ressourcen, die im try
-Block verwendet werden, zu bereinigen.
Hinweis
Wenn der Typ einer Ressource die Schnittstelle IDisposable oder IAsyncDisposable implementiert, sollten Sie die using
-Anweisung berücksichtigen. Mit der using
-Anweisung wird sichergestellt, dass abgerufene Ressourcen verworfen werden, wenn die Steuerung die using
-Anweisung verlässt. Der Compiler transformiert eine using
-Anweisung in eine try-finally
-Anweisung.
Der finally
-Block wird je nachdem, ob vom Betriebssystem ein Entladevorgang für die Ausnahme ausgelöst wird, ausgeführt. Die einzigen Fälle, in denen finally
-Blöcke nicht ausgeführt werden, betreffen die sofortige Beendigung eines Programms. Eine solche Beendigung kann beispielsweise aufgrund des Environment.FailFast-Aufrufs oder einer OverflowException- oder InvalidProgramException-Ausnahme auftreten. Bei den meisten Betriebssystemen findet eine angemessene Ressourcenbereinigung statt, während der Prozess beendet und entladen wird.
Die Anweisung try-catch-finally
Eine try-catch-finally
-Anweisung wird verwendet, um Ausnahmen zu behandeln, die während der Ausführung des try
-Blocks auftreten können, und um den Code anzugeben, der ausgeführt werden muss, wenn die Steuerung die try
-Anweisung verlässt:
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;
}
}
Wenn eine Ausnahme von einem catch
-Block behandelt wird, wird der finally
-Block nach der Ausführung dieses catch
-Blocks ausgeführt (auch wenn während der Ausführung des catch
-Blocks eine andere Ausnahme auftritt). Informationen zum catch
-Block und zum finally
-Block finden Sie in den Abschnitten Die try-catch
-Anweisung bzw. Die try-finally
-Anweisung.
C#-Sprachspezifikation
Weitere Informationen finden Sie in den folgenden Abschnitten der C#-Sprachspezifikation: