Ausnahmebehandlungsanweisungen: throw, try-catch, try-finally und try-catch-finally

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 stellt außerdem die Hilfsmethoden zum Auslösen von Ausnahmen unter bestimmten Bedingungen bereit: 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 übergebene args-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.

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 oder goto)
  • 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.

In fast allen Fällen werden finally-Blöcke 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:

Weitere Informationen