Ausnahmebehandlung (C#-Programmierhandbuch)

Ein try-Block wird von C#-Programmierern verwendet, um Code zu partitionieren, der von einer Ausnahme betroffen sein könnte. Zugeordnete catch-Blöcke werden verwendet, um die sich ergebenden Ausnahmen zu behandeln. Ein finally-Block enthält Code, der unabhängig davon ausgeführt wird, ob eine Ausnahme im try-Block ausgelöst wird, z. B. beim Freigeben von Ressourcen, die im try-Block zugewiesen sind. Ein try-Block erfordert einen oder mehrere zugeordnete catch-Blöcke oder ein finally-Block oder beides.

Die folgenden Beispiele zeigen eine try-catch-Anweisung eine try-finally-Anweisung und eine try-catch-finally-Anweisung.

try
{
    // Code to try goes here.
}
catch (SomeSpecificException ex)
{
    // Code to handle the exception goes here.
    // Only catch exceptions that you know how to handle.
    // Never catch base class System.Exception without
    // rethrowing it at the end of the catch block.
}
try
{
    // Code to try goes here.
}
finally
{
    // Code to execute after the try block goes here.
}
try
{
    // Code to try goes here.
}
catch (SomeSpecificException ex)
{
    // Code to handle the exception goes here.
}
finally
{
    // Code to execute after the try (and possibly catch) blocks
    // goes here.
}

Ein try-Block ohne einen catch- oder finally-Block verursachen einen Compilerfehler.

catch-Blöcke

Ein catch-Block kann den Typ der Ausnahme angeben, die abgefangen werden soll. Die Typspezifikation wird einen Ausnahmefilter aufrufen. Der Typ der Ausnahme sollte von Exception abgeleitet werden. Im Allgemeinen sollten Sie Exception nur als Ausnahmefilter festlegen, wenn Sie entweder alle Ausnahmen behandeln können, die möglicherweise im try-Block ausgelöst werden, oder eine throw-Anweisung am Ende des catch-Blocks eingeschlossen haben.

Mehrere catch-Blöcke mit verschiedenen Ausnahmeklassen können miteinander verkettet werden. Die catch-Blöcke werden von oben nach unten in Ihrem Code überprüft, aber nur ein catch-Block wird für jede Ausnahme, die ausgelöst wird, ausgeführt. Der erste catch-Block, der den exakten Typ oder eine Basisklasse der ausgelösten Ausnahme angibt, wird ausgeführt. Wenn kein catch-Block eine passende Ausnahmeklasse angibt, wird ein catch-Block, der über keinen Typ verfügt, ausgewählt, wenn einer in der Anweisung vorhanden ist. Es ist wichtig, catch-Blöcke mit den spezifischsten (d. h. am stärksten abgeleiteten) Ausnahmeklassen als erstes zu positionieren.

Fangen Sie Ausnahmen ab, wenn die folgenden Bedingungen erfüllt sind:

  • Sie verstehen, warum die Ausnahme ausgelöst werden kann, und Sie können eine bestimmte Wiederherstellung implementieren, wie z.B. den Benutzer auffordern, einen neuen Dateinamen einzugeben, wenn Sie ein Objekt FileNotFoundException abfangen.
  • Sie können eine neue und spezifischere Ausnahme erstellen und auslösen.
    int GetInt(int[] array, int index)
    {
        try
        {
            return array[index];
        }
        catch (IndexOutOfRangeException e)
        {
            throw new ArgumentOutOfRangeException(
                "Parameter index is out of range.", e);
        }
    }
    
  • Sie möchten eine Ausnahme teilweise behandeln, bevor Sie sie zur weiteren Behandlung weitergeben. Im folgenden Beispiel wird ein catch-Block verwendet, um dem Fehlerprotokoll einen Eintrag hinzufügen, bevor die Ausnahme wieder ausgelöst wird.
    try
    {
        // Try to access a resource.
    }
    catch (UnauthorizedAccessException e)
    {
        // Call a custom error logging procedure.
        LogError(e);
        // Re-throw the error.
        throw;
    }
    

Sie können auch Ausnahmefilter festlegen, um einen booleschen Ausdruck zu einer Catch-Klausel hinzuzufügen. Ausnahmefilter geben an, dass eine spezifische „catch“-Klausel nur übereinstimmt, wenn diese Bedingung erfüllt ist. Im folgenden Beispiel verwenden beide „catch“-Klauseln dieselbe Ausnahmeklasse, jedoch wird eine zusätzliche Bedingung überprüft, um eine andere Fehlermeldung zu erstellen:

int GetInt(int[] array, int index)
{
    try
    {
        return array[index];
    }
    catch (IndexOutOfRangeException e) when (index < 0) 
    {
        throw new ArgumentOutOfRangeException(
            "Parameter index cannot be negative.", e);
    }
    catch (IndexOutOfRangeException e)
    {
        throw new ArgumentOutOfRangeException(
            "Parameter index cannot be greater than the array size.", e);
    }
}

Ein Ausnahmefilter, der immer false zurückgibt, kann dazu verwendet werden, alle Ausnahmen zu untersuchen, aber nicht zu verarbeiten. Ein üblicher Anwendungsfall ist die Protokollierung von Ausnahmen:

public class ExceptionFilter
{
    public static void Main()
    {
        try
        {
            string? s = null;
            Console.WriteLine(s.Length);
        }
        catch (Exception e) when (LogException(e))
        {
        }
        Console.WriteLine("Exception must have been handled");
    }

    private static bool LogException(Exception e)
    {
        Console.WriteLine($"\tIn the log routine. Caught {e.GetType()}");
        Console.WriteLine($"\tMessage: {e.Message}");
        return false;
    }
}

Die Methode LogException gibt immer false zurück. Es besteht keine Übereinstimmung mit einer catch-Klausel, die diesen Ausnahmefilter verwendet. Die Catch-Klausel kann allgemein sein und System.Exception verwenden. Spätere Klauseln können spezifischere Ausnahmeklassen verarbeiten.

Finally-Blöcke

Mit einem finally-Block können Sie Aktionen bereinigen, die in einem try-Block ausgeführt werden. Falls vorhanden, wird der finally-Block zuletzt ausgeführt, nach dem try-Block und jedem übereinstimmenden catch Block. Ein finally-Block wird immer ausgeführt, unabhängig davon, ob eine Ausnahme ausgelöst wird oder ein catch-Block gefunden wird, der mit dem Ausnahmetyp übereinstimmt.

Der finally-Block kann zum Freigeben von Ressourcen verwendet werden, wie z.B. Dateistreams, Datenbankverbindungen und Grafikhandles, ohne Warten auf den Garbage Collector in der Laufzeit, um die Objekte zu beenden.

Im folgenden Beispiel wird der finally-Block verwendet, um eine Datei, die im try-Block geöffnet ist, zu schließen. Beachten Sie, dass der Status des Dateihandles überprüft wird, bevor die Datei geschlossen wird. Wenn der try-Block die Datei nicht öffnen kann, verfügt das Dateihandle immer noch über den Wert null, und der finally-Block versucht nicht, die Datei zu schließen. Stattdessen schließt der finally-Block die geöffnete Datei, wenn die Datei im try-Block erfolgreich geöffnet wird.

FileStream? file = null;
FileInfo fileinfo = new System.IO.FileInfo("./file.txt");
try
{
    file = fileinfo.OpenWrite();
    file.WriteByte(0xF);
}
finally
{
    // Check for null because OpenWrite might have failed.
    file?.Close();
}

C#-Programmiersprachenspezifikation

Weitere Informationen finden Sie unter Ausnahmen und Die try-Anweisung in der C#-Sprachspezifikation. Die Sprachspezifikation ist die verbindliche Quelle für die Syntax und Verwendung von C#.

Siehe auch