Erstellen und Auslösen von Ausnahmen

Ausnahmen werden verwendet, um anzugeben, dass während der Ausführung des Programms ein Fehler aufgetreten ist. Ausnahmeobjekte, die einen Fehler beschreiben, werden erstellt und dann per throw-Anweisung oder -Ausdruckausgelöst. Die Laufzeit sucht dann nach dem kompatibelsten Ausnahmehandler.

Programmierer sollten Ausnahmen auslösen, wenn eine oder mehrere der folgenden Bedingungen wahr sind:

  • Die Methode kann ihre definierte Funktionalität nicht abschließen. Wenn beispielsweise ein Parameter einer Methode einen ungültigen Wert hat:

    static void CopyObject(SampleClass original)
    {
        _ = original ?? throw new ArgumentException("Parameter cannot be null", nameof(original));
    }
    
  • Ein unpassender Aufruf eines Objekts erfolgt, basierend auf dem Objektzustand. Ein Beispiel wäre ein Versuch, in eine schreibgeschützte Datei zu schreiben. Lösen Sie in Fällen, in denen ein Objektzustand keinen Vorgang erlaubt, eine Instanz von InvalidOperationException oder ein Objekt aus, das auf einer Ableitung dieser Klasse basiert. Der folgende Code ist ein Beispiel für eine Methode, die ein InvalidOperationException-Objekt auslöst:

    public class ProgramLog
    {
        FileStream logFile = null!;
        public void OpenLog(FileInfo fileName, FileMode mode) { }
    
        public void WriteLog()
        {
            if (!logFile.CanWrite)
            {
                throw new InvalidOperationException("Logfile cannot be read-only");
            }
            // Else write data to the log and return.
        }
    }
    
  • Wenn ein Argument an eine Methode eine Ausnahme auslöst. In diesem Fall muss die ursprüngliche Ausnahme abgefangen und eine ArgumentException-Instanz erstellt werden. Die ursprüngliche Ausnahme sollte an den Konstruktor der ArgumentException als InnerException-Parameter übergeben werden:

    static int GetValueFromArray(int[] array, int index)
    {
        try
        {
            return array[index];
        }
        catch (IndexOutOfRangeException e)
        {
            throw new ArgumentOutOfRangeException(
                "Parameter index is out of range.", e);
        }
    }
    

    Hinweis

    Das vorangehende Beispiel zeigt, wie Sie die Eigenschaft InnerException verwenden. Es ist absichtlich vereinfacht. In der Praxis sollten Sie prüfen, ob ein Index im Bereich liegt, bevor Sie ihn verwenden. Sie können diese Technik des Umschließens einer Ausnahme verwenden, wenn ein Element eines Parameters eine Ausnahme auslöst, die Sie vor dem Aufruf des Elements nicht vorhersehen konnten.

Ausnahmen enthalten eine Eigenschaft mit dem Namen StackTrace. Diese Zeichenfolge enthält den Namen der Methoden für die aktuelle Aufrufliste, zusammen mit dem Dateinamen und der Zeilennummer, in der die Ausnahme für jede Methode ausgelöst wurde. Ein StackTrace-Objekt wird automatisch von der Common Language Runtime (CLR) ab der throw-Anweisung erstellt, sodass Ausnahmen ab dem Punkt ausgelöst werden müssen, an dem die Stapelüberwachung beginnen soll.

Alle Ausnahmen enthalten eine Eigenschaft mit dem Namen Message. Diese Zeichenfolge sollte festgelegt werden, um die Gründe für die Ausnahme zu erklären. Vertrauliche Informationen dürfen aus Sicherheitsgründen nicht in den Nachrichtentext eingefügt werden. Zusätzlich zu Message enthält ArgumentException eine Eigenschaft mit dem Namen ParamName, die auf den Namen des Arguments festgelegt werden sollte, das die Ausnahme ausgelöst hat. Bei einem Eigenschaftensetter sollte ParamName auf value festgelegt werden.

Öffentliche und geschützte Methoden lösen Ausnahmen aus, wenn sie ihre Funktionen nicht durchführen können. Die ausgelöste Ausnahmeklasse ist die verfügbare Ausnahme, die am besten zu den vorliegenden Fehlerbedingungen passt. Diese Ausnahmen sollten als Teil der Klassenfunktionalität dokumentiert werden, und abgeleitete Klassen oder Updates an der ursprünglichen Klasse müssen das gleiche Verhalten für die Abwärtskompatibilität beibehalten.

Was Sie beim Auslösen von Ausnahmen vermeiden sollten

Die folgende Liste enthält Vorgehensweisen, die beim Auslösen von Ausnahmen zu vermeiden sind:

  • Verwenden Sie Ausnahmen nicht, um den Ablauf eines Programms als Teil der normalen Ausführung zu ändern. Verwenden Sie Ausnahmen, um Fehlerbedingungen zu melden und zu behandeln.
  • Ausnahmen sollten nicht als Rückgabewert oder Parameter zurückgegeben werden, sie sollten ausgelöst werden.
  • Lösen Sie System.Exception, System.SystemException, System.NullReferenceException oder System.IndexOutOfRangeException nicht absichtlich über Ihren eigenen Quellcode aus.
  • Erstellen Sie keine Ausnahmen, die im Debugmodus, aber nicht im Releasemodus ausgelöst werden können. Um Laufzeitfehler während der Entwicklungsphase zu identifizieren, verwenden Sie stattdessen die Debugassertion.

Ausnahmen in Aufgaben zurückgebenden Methoden

Methoden, die mit dem async-Modifizierer deklariert werden, implizieren einige besondere Überlegungen, wenn es um Ausnahmen geht. Ausnahmen, die in einer async-Methode ausgelöst werden, werden in der zurückgegebenen Aufgabe gespeichert und treten erst auf, wenn die Aufgabe erwartet wird. Weitere Informationen zu gespeicherten Ausnahmen finden Sie unter Asynchrone Ausnahmen.

Es wird empfohlen, Argumente zu überprüfen und entsprechende Ausnahmen wie ArgumentException und ArgumentNullException auszulösen, bevor Sie die asynchronen Teile Ihrer Methoden eingeben. Das heißt, diese Validierungsausnahmeregelungen sollten synchron auftreten, bevor die Arbeit beginnt. Der folgende Codeschnipsel zeigt ein Beispiel, bei dem, wenn die Ausnahmen ausgelöst werden, die ArgumentException-Ausnahmen synchron auftreten würden, während die InvalidOperationException-Ausnahmen in der zurückgegebenen Aufgabe gespeichert würden.

// Non-async, task-returning method.
// Within this method (but outside of the local function),
// any thrown exceptions emerge synchronously.
public static Task<Toast> ToastBreadAsync(int slices, int toastTime)
{
    if (slices is < 1 or > 4)
    {
        throw new ArgumentException(
            "You must specify between 1 and 4 slices of bread.",
            nameof(slices));
    }

    if (toastTime < 1)
    {
        throw new ArgumentException(
            "Toast time is too short.", nameof(toastTime));
    }

    return ToastBreadAsyncCore(slices, toastTime);

    // Local async function.
    // Within this function, any thrown exceptions are stored in the task.
    static async Task<Toast> ToastBreadAsyncCore(int slices, int time)
    {
        for (int slice = 0; slice < slices; slice++)
        {
            Console.WriteLine("Putting a slice of bread in the toaster");
        }
        // Start toasting.
        await Task.Delay(time);

        if (time > 2_000)
        {
            throw new InvalidOperationException("The toaster is on fire!");
        }

        Console.WriteLine("Toast is ready!");

        return new Toast();
    }
}

Definieren von Ausnahmeklassen

Programme können eine zuvor definierte Ausnahmeklasse im System-Namespace auslösen (mit Ausnahme der eben beschriebenen Fälle) oder ihre eigenen Ausnahmeklassen durch Ableitung von Exception erstellen. Die abgeleiteten Klassen sollten mindestens drei Konstruktoren definieren: einen Konstruktor ohne Parameter, einen zum Festlegen der Eigenschaft „message“ und einen Konstruktor zum Festlegen der beiden Eigenschaften Message und InnerException. Zum Beispiel:

[Serializable]
public class InvalidDepartmentException : Exception
{
    public InvalidDepartmentException() : base() { }
    public InvalidDepartmentException(string message) : base(message) { }
    public InvalidDepartmentException(string message, Exception inner) : base(message, inner) { }
}

Fügen Sie neue Eigenschaften zur Ausnahmeklasse hinzu, wenn diese Daten bereitstellen, die zum Auflösen der Ausnahme nützlich sind. Wenn der abgeleiteten Ausnahmeklasse neue Eigenschaften hinzugefügt werden, muss ToString() überschrieben werden, um die hinzugefügten Informationen zurückzugeben.

C#-Sprachspezifikation

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

Siehe auch