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:
- Saubererem und besser lesbarem Error Handling
- Präziserer Kontrolle über die Exception-Logik
- Weniger verschachtelten
if-Anweisungen innerhalb voncatch-Blöcken
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:
- Wenn die API
404 Not Foundzurückgibt, wird eine spezifische Meldung angezeigt. - Wenn die API
401 Unauthorizedzurückgibt, wird dies separat behandelt. - Andere HTTP-bezogene Fehler werden im allgemeinen
HttpRequestException-Block abgefangen.
- Die
when-Bedingung wird vor dem Betreten descatch-Blocks ausgewertet. - Ist die Bedingung
false, überspringt die Runtime diesecatch-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.
-
throw;Löst die Exception erneut aus und bewahrt dabei die ursprüngliche Quelle (Zeilennummer und Methode, in der der Fehler entstanden ist). Beim Debugging kann genau nachvollzogen werden, wo die Exception tatsächlich begonnen hat. Dies ist die korrekte und empfohlene Vorgehensweise. -
throw ex;Setzt die Stack Trace an dieser Stelle zurück und lässt es so erscheinen, als ob der Fehler imcatch-Block entstanden wäre. Dadurch geht die eigentliche Fehlerquelle (zum Beispiel eine tiefer liegende Methode) verloren, was die Fehlersuche erschwert.
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
throwkö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:
- Ermöglicht eine klarere Trennung verschiedener Fehlertypen
- Bietet spezifischere Behandlungsmöglichkeiten in
catch-Blöcken - Liefert zusätzliche kontextbezogene Informationen für Logging und Analyse
- Verbessert die Lesbarkeit und Wartbarkeit des Codes
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
C# Schleifen (for, foreach, while, do-while)
Lernen Sie die Verwendung von for-, foreach-, while- und do-while-Schleifen in C#, um wiederholte Abläufe effizient umzusetzen.
C# Verzweigungsstrukturen (if, else, switch)
Entscheidungsstrukturen in C#: lernen Sie, wie man mit if, else if, else und switch unterschiedliche Aktionen abhängig von Bedingungen ausführt.
Interop in C# (Arbeiten mit C/C++-Bibliotheken)
Lernen Sie Interop in C#, um mit C/C++-Bibliotheken zu arbeiten, einschließlich P/Invoke und unmanaged Code.
Methoden und Parameterverwendung in C#
Lernen Sie Methoden und die Verwendung von Parametern in C#, einschließlich Wert- und Referenzparametern sowie optionalen Parametern.
SOLID-Prinzipien mit C#
SOLID-Prinzipien mit C#-Beispielen: flexiblen, wartbaren und testbaren Code erstellen.