Freigeben über


Verwenden von Ausnahmen

In C# werden Fehler im Programm zur Laufzeit mithilfe eines Mechanismus, der als Ausnahmen bezeichnet wird, über das Programm verteilt. Ausnahmen werden von Code ausgelöst, der auf einen Fehler stößt. Sie werden von Code abgefangen, der den Fehler beheben kann. Ausnahmen können durch die .NET-Runtime oder durch Code in einem Programm ausgelöst werden. Sobald eine Ausnahme ausgelöst wird, breitet sie sich durch den Aufrufstapel nach oben aus, bis eine catch Anweisung für die Ausnahme gefunden wird. Nicht abgefangene Ausnahmen werden von einem allgemeinen Ausnahmebehandler behandelt, der vom System bereitgestellt wird und einen Dialogfenster anzeigt.

Ausnahmen werden durch Klassen repräsentiert, die von Exception abgeleitet sind. Diese Klasse identifiziert den Ausnahmetyp und enthält Eigenschaften, die Details zur Ausnahme enthalten. Das Auslösen einer Ausnahme umfasst das Erstellen einer Instanz einer ausnahme abgeleiteten Klasse, optional das Konfigurieren von Eigenschaften der Ausnahme und das anschließende Auslösen des Objekts mithilfe des throw Schlüsselworts. Beispiel:

class CustomException : Exception
{
    public CustomException(string message)
    {
    }
}
private static void TestThrow()
{
    throw new CustomException("Custom exception in TestThrow()");
}

Nachdem eine Ausnahme ausgelöst wurde, überprüft die Laufzeit die aktuelle Anweisung, um festzustellen, ob sie sich innerhalb eines try Blocks befindet. Wenn dies der Fall ist, werden alle catch zugeordneten try Blöcke überprüft, um festzustellen, ob sie die Ausnahme abfangen können. Catch Blöcke geben in der Regel Ausnahmetypen an; wenn der Typ des catch Blocks derselbe Typ wie die Ausnahme ist oder eine Basisklasse der Ausnahme, kann der catch Block die Methode behandeln. Beispiel:

try
{
    TestThrow();
}
catch (CustomException ex)
{
    System.Console.WriteLine(ex.ToString());
}

Wenn sich die Anweisung, die eine Ausnahme auslöst, nicht innerhalb eines try Blocks befindet oder wenn der try eingeschlossene Block keinen übereinstimmenden catch Block aufweist, überprüft die Laufzeit die aufrufende Methode auf eine try Anweisung und catch Blöcke. Die Laufzeit durchläuft den Aufrufstapel weiter und sucht nach einem kompatiblen catch-Block. Nachdem der catch Block gefunden und ausgeführt wurde, wird das Steuerelement an die nächste Anweisung nach diesem catch Block übergeben.

Eine try Anweisung kann mehr als einen catch Block enthalten. Die erste catch Anweisung, die die Ausnahme behandeln kann, wird ausgeführt. Alle folgenden catch Anweisungen, auch wenn sie kompatibel sind, werden ignoriert. Sortieren Sie Catch-Blöcke immer vom spezifischsten (oder am meisten abgeleiteten) zum am wenigsten spezifischen Element. Beispiel:

using System;
using System.IO;

namespace Exceptions
{
    public class CatchOrder
    {
        public static void Main()
        {
            try
            {
                using (var sw = new StreamWriter("./test.txt"))
                {
                    sw.WriteLine("Hello");
                }
            }
            // Put the more specific exceptions first.
            catch (DirectoryNotFoundException ex)
            {
                Console.WriteLine(ex);
            }
            catch (FileNotFoundException ex)
            {
                Console.WriteLine(ex);
            }
            // Put the least specific exception last.
            catch (IOException ex)
            {
                Console.WriteLine(ex);
            }
            Console.WriteLine("Done");
        }
    }
}

Bevor der catch Block ausgeführt wird, sucht die Laufzeit nach finally Blöcken. Finally Blöcke ermöglichen es dem Programmierer, alle mehrdeutigen Zustände zu bereinigen, die von einem abgebrochenen try Block übrig bleiben könnten, oder externe Ressourcen (z. B. Grafikhandles, Datenbankverbindungen oder Dateidatenströme) freizugeben, ohne auf den Garbage Collector in der Laufzeit zu warten, um die Objekte abzuschließen. Beispiel:

static void TestFinally()
{
    FileStream? file = null;
    //Change the path to something that works on your machine.
    FileInfo fileInfo = new System.IO.FileInfo("./file.txt");

    try
    {
        file = fileInfo.OpenWrite();
        file.WriteByte(0xF);
    }
    finally
    {
        // Closing the file allows you to reopen it immediately - otherwise IOException is thrown.
        file?.Close();
    }

    try
    {
        file = fileInfo.OpenWrite();
        Console.WriteLine("OpenWrite() succeeded");
    }
    catch (IOException)
    {
        Console.WriteLine("OpenWrite() failed");
    }
}

Falls WriteByte() eine Ausnahme ausgelöst, schlägt der Code im zweiten try Block, der versucht, die Datei erneut zu öffnen, fehl, sofern file.Close() nicht aufgerufen wird, und die Datei bleibt gesperrt. Da finally Blöcke auch dann ausgeführt werden, wenn eine Ausnahme ausgelöst wird, lässt der finally Block im vorherigen Beispiel zu, dass die Datei korrekt geschlossen und ein Fehler vermieden wird.

Wenn kein kompatibler catch Block im Aufrufstapel gefunden wird, nachdem eine Ausnahme ausgelöst wurde, tritt eins von drei Elementen auf:

  • Wenn sich die Ausnahme innerhalb eines Finalizers befindet, wird der Finalizer abgebrochen, und der Basis-Finalizer wird (falls vorhanden) aufgerufen.
  • Wenn der Aufrufstapel einen statischen Konstruktor oder einen Initialisierer für statische Felder enthält, wird ein TypeInitializationException geworfen, wobei die ursprüngliche Ausnahme der InnerException-Eigenschaft der neuen Ausnahme zugewiesen wird.
  • Wenn der Start des Threads erreicht ist, wird der Thread beendet.