Wird geladen...

Ausnahmebehandlung in C# (try, catch, finally)

Erlernen Sie die Ausnahmebehandlung in C# mit try-, catch- und finally-Blöcken zur sicheren Fehlerverwaltung anhand von Beispielen.

In C# dient die Fehlerbehandlung dazu, unerwartete Situationen während der Programmausführung abzufangen und zu kontrollieren. So stürzt das Programm nicht plötzlich ab, sondern kann dem Benutzer eine passende Meldung anzeigen oder alternative Aktionen ausführen. Die Fehlerbehandlung verwendet die Blöcke try, catch und finally.


Verwendung von try-catch

Im try-Block stehen Anweisungen, die eine Ausnahme auslösen können. Tritt ein Fehler auf, wird der catch-Block ausgeführt.


try
{
    int zahl = int.Parse("abc"); // Ungültige Konvertierung
    Console.WriteLine("Zahl: " + zahl);
}
catch (FormatException ex)
{
    Console.WriteLine("Fehler: Ungültiges Zahlenformat.");
}
// Ausgabe:
Fehler: Ungültiges Zahlenformat.

Mehrere catch-Blöcke

Für verschiedene Fehlertypen können mehrere catch-Blöcke verwendet werden. So lässt sich jeder Fehlertyp unterschiedlich behandeln.


try
{
    int[] zahlen = { 1, 2, 3 };
    Console.WriteLine(zahlen[5]); // Index außerhalb des Bereichs
}
catch (IndexOutOfRangeException)
{
    Console.WriteLine("Fehler: Array-Index außerhalb des Bereichs.");
}
catch (Exception ex)
{
    Console.WriteLine("Unerwarteter Fehler: " + ex.Message);
}

Erweiterte Fehlerbehandlung: Exception Filters (when)

In C# ermöglichen Exception Filter, einem catch-Block mit dem Schlüsselwort when eine Bedingung hinzuzufügen. Dadurch kann eine Ausnahme nur dann abgefangen werden, wenn eine bestimmte Bedingung erfüllt ist.

Der wichtigste Vorteil von Exception Filtern besteht darin, dass die Bedingung vor dem Betreten des catch-Blocks ausgewertet wird. Wenn die Bedingung false ergibt, überspringt die Runtime diesen Block und sucht weiter nach einer passenden catch-Klausel.

Das führt zu:

Beispiel: HTTP-Fehler anhand des Statuscodes behandeln

In realen Anwendungen liefern HTTP-Anfragen häufig unterschiedliche Statuscodes zurück. Mithilfe von Exception Filtern können bestimmte Statuscodes separat behandelt werden, während der Code strukturiert und gut lesbar bleibt.


using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        using var client = new HttpClient();

        try
        {
            HttpResponseMessage response =
                await client.GetAsync("https://api.example.com/users/1");

            response.EnsureSuccessStatusCode();

            string content = await response.Content.ReadAsStringAsync();
            Console.WriteLine(content);
        }
        catch (HttpRequestException ex) 
            when (ex.StatusCode == HttpStatusCode.NotFound)
        {
            Console.WriteLine("Ressource nicht gefunden (404).");
        }
        catch (HttpRequestException ex) 
            when (ex.StatusCode == HttpStatusCode.Unauthorized)
        {
            Console.WriteLine("Unbefugter Zugriff (401). Bitte überprüfen Sie Ihre Anmeldedaten.");
        }
        catch (HttpRequestException ex)
        {
            Console.WriteLine("Ein allgemeiner HTTP-Fehler ist aufgetreten.");
            Console.WriteLine($"Statuscode: {ex.StatusCode}");
        }
    }
}
  

In diesem Beispiel:

  • Die when-Bedingung wird vor dem Betreten des catch-Blocks ausgewertet.
  • Ist die Bedingung false, überspringt die Runtime diese catch-Klausel.
  • Exception Filter wurden mit C# 6 eingeführt und werden häufig in moderner Backend-Entwicklung verwendet.

Rethrowing Exceptions: throw; vs throw ex;

Eine in einem catch-Block abgefangene Exception kann erneut ausgelöst werden. Allerdings verhalten sich throw; und throw ex; nicht gleich.

Der grundlegende Unterschied besteht darin, ob die Stack Trace-Information erhalten bleibt oder nicht. Die Stack Trace zeigt die Methodenaufrufkette, die zum Fehler geführt hat, sowie die genaue Zeile, in der er ursprünglich entstanden ist.

Beispiel


using System;

class Program
{
    static void Main()
    {
        try
        {
            MethodA();
        }
        catch (Exception ex)
        {
            Console.WriteLine("Fehler wurde protokolliert.");

            // Korrekte Verwendung:
            throw;

            // Falsche Verwendung:
            // throw ex;
        }
    }

    static void MethodA()
    {
        MethodB();
    }

    static void MethodB()
    {
        throw new InvalidOperationException("Ein Fehler ist aufgetreten.");
    }
}
  

In diesem Beispiel wird bei Verwendung von throw; die Exception in der Stack Trace so angezeigt, als wäre sie in MethodB entstanden.

Wird stattdessen throw ex; verwendet, erscheint es so, als ob die Exception im catch-Block entstanden wäre, und die ursprüngliche Aufrufkette geht verloren.

  • Wenn eine Exception erneut ausgelöst werden soll, sollte immer throw; verwendet werden.
  • throw ex; erschwert das Debugging, da die ursprüngliche Stack Trace verloren geht.
  • Mit dem Schlüsselwort throw können auch benutzerdefinierte Exception-Klassen ausgelöst werden.

Custom Exceptions

In realen Anwendungen sollten nicht alle Fehler mit allgemeinen Exception-Typen wie Exception oder anderen integrierten Exceptions dargestellt werden. Um Geschäftsregeln (Business Logic) und domänenspezifische Probleme klarer auszudrücken, können benutzerdefinierte Exception-Klassen erstellt werden.

Dieser Ansatz:

Beispiel


// 1. Definition benutzerdefinierter Exceptions
public class ProjectNotFoundException : Exception
{
    public ProjectNotFoundException(string message) : base(message) { }
}

public class ProjectIsCanceledException : Exception
{
    public int ProjectId { get; }
    public string ProjectName { get; }

    public ProjectIsCanceledException(int projectId, string projectName)
        : base($"Project '{projectName}' (Id: {projectId}) is canceled and cannot be processed.")
    {
        ProjectId = projectId;
        ProjectName = projectName;
    }
}

// 2. Verwendung
public void ProcessProject(Project project)
{
    if (project == null)
    {
        throw new ProjectNotFoundException("Projekt ist im System nicht registriert.");
    }

    if (project.Status == ProjectStatus.Canceled)
    {
        throw new ProjectIsCanceledException(project.Id, project.Name);
    }

    // ...
}
  

In diesem Beispiel wird eine ProjectNotFoundException ausgelöst, wenn das Projekt im System nicht vorhanden ist.

Falls das Projekt storniert wurde, wird eine ProjectIsCanceledException ausgelöst. Diese Exception enthält nicht nur eine Fehlermeldung, sondern auch die Werte ProjectId und ProjectName, wodurch Logging und Fehleranalyse aussagekräftiger werden.

  • Benutzerdefinierte Exception-Klassen repräsentieren in der Regel Geschäftsregel- oder Domänenverstöße.
  • Die Benennung folgt üblicherweise dem Muster SomethingException.
  • Zusätzliche Eigenschaften können hinzugefügt werden, um mehr Kontextinformationen bereitzustellen.

finally-Block

Der finally-Block wird immer ausgeführt, egal ob ein Fehler auftritt oder nicht. Er wird häufig für Aufräumarbeiten wie das Schließen von Dateien oder das Beenden von Datenbankverbindungen verwendet.


try
{
    Console.WriteLine("Datei wird geöffnet...");
    throw new Exception("Datei nicht gefunden!");
}
catch (Exception ex)
{
    Console.WriteLine("Fehler: " + ex.Message);
}
finally
{
    Console.WriteLine("Datei wird geschlossen...");
}
// Ausgabe:
Datei wird geöffnet...
Fehler: Datei nicht gefunden!
Datei wird geschlossen...

Eigene Exceptions werfen

Eigene Fehlerbedingungen können definiert und mit throw ausgelöst werden.


static void Teile(int a, int b)
{
    if (b == 0)
        throw new DivideByZeroException("Fehler: Division durch Null!");
    Console.WriteLine("Ergebnis: " + (a / b));
}

static void Main()
{
    try
    {
        Teile(10, 0);
    }
    catch (DivideByZeroException ex)
    {
        Console.WriteLine("Fehler: " + ex.Message);
    }
}
// Ausgabe:
Fehler: Division durch Null!

TL;DR

  • try: Enthält Code, der Exceptions auslösen kann.
  • catch: Fängt Exceptions ab und behandelt sie.
  • finally: Wird immer ausgeführt, meist für Aufräumarbeiten.
  • Mehrere catch-Blöcke ermöglichen die getrennte Behandlung verschiedener Exception-Typen.
  • Exception-Filter (when) erlauben eine bedingte Exception-Behandlung.
  • throw; bewahrt die ursprüngliche Stack Trace beim erneuten Auslösen.
  • throw ex; setzt die Stack Trace zurück und sollte in der Regel vermieden werden.
  • Benutzerdefinierte Exception-Klassen repräsentieren domänenspezifische oder Geschäftsregel-Fehler.


Ähnliche Artikel