1. Einführung
Sobald dein Programm ein bisschen komplexer wird als nur Hello, world!, stehst du schnell vor der Aufgabe: Fehler verschiedener Typen unterschiedlich zu behandeln. Stell dir vor: Du arbeitest mit Dateien, Netzwerk, Datenbank – und für verschiedene Fehler brauchst du jeweils eine andere Reaktion. Zum Beispiel: Wenn eine Datei fehlt, kannst du dem User anbieten, eine andere auszuwählen. Bei einem Netzwerkfehler – vielleicht die Aktion wiederholen oder eine nette Nachricht anzeigen wie "Check mal das Kabel, vielleicht hat die Katze wieder dran geknabbert".
In C# nutzt man dafür mehrere catch-Blöcke hintereinander. Jeder Block ist auf einen bestimmten Exception-Typ (und dessen Nachfolger) spezialisiert.
Struktur von mehreren catch-Blöcken
try
{
// Hier kommt Code, der verschiedene Exceptions werfen kann
}
catch (FileNotFoundException ex)
{
Console.WriteLine($"Datei nicht gefunden: {ex.Message}");
}
catch (IOException ex) // fängt alle IO-Fehler, außer FileNotFoundException
{
Console.WriteLine($"Eingabe-/Ausgabefehler: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"Irgendwas ist schiefgelaufen: {ex.Message}");
}
Wichtig! Der Compiler geht die Blöcke von oben nach unten durch und nimmt den ersten, der zum Typ passt. Wenn FileNotFoundException gefangen wurde, geht’s nicht weiter die Kette runter.
Illustration der "Kette"
| Exception-Typ | Welcher Block fängt? |
|---|---|
| FileNotFoundException | Erster catch |
| IOException (anderer) | Zweiter catch |
| ArgumentException | Dritter (allgemeiner) catch |
Warum nicht mischen?
Die "breiteste" Falle – zum Beispiel catch (Exception) – solltest du immer zuletzt setzen, sonst schluckt sie alle Exceptions zu früh und die spezialisierten catch-Blöcke werden nie erreicht.
Das ist wie alle Feuermelder im Gebäude auszuschalten, nur weil einer wegen einem angebrannten Toast ausgelöst hat: Dann merkt das System spätere Brände nicht mehr.
Praxibeispiel
using System;
using System.IO;
class Program
{
static void Main()
{
try
{
double result = CalculateAverageAgeFromFile("users.txt");
Console.WriteLine($"Durchschnittsalter — {result}");
}
catch (FileNotFoundException ex)
{
Console.WriteLine("Fehler: Datei nicht gefunden. Pfad prüfen.");
}
catch (FormatException ex)
{
Console.WriteLine("Datenfehler: Alter konnte nicht gelesen werden.");
}
catch (Exception ex)
{
Console.WriteLine($"Anderer Fehler: {ex.Message}");
}
}
static double CalculateAverageAgeFromFile(string filePath)
{
// (Implementierung: liest Datei, parst Alter, berechnet Durchschnitt)
// ...
throw new NotImplementedException();
}
}
Hier trennen wir klar, was zu tun ist, wenn die Datei fehlt (FileNotFoundException) und was, wenn die Daten in der Datei "krumm" sind (FormatException). Alles andere fängt der "Backup"-Block ganz unten ab.
2. catch-Filter: Feine Nuancen abfangen
Mehrere catch-Blöcke sind praktisch, aber manchmal reicht das nicht. Es gibt Fälle, wo du innerhalb eines Exception-Typs unterschiedlich reagieren willst – je nach Umständen.
Zum Beispiel: Wenn das Netzwerk nicht geht, weil das Internet alle ist – kannst du einen Reconnect versuchen. Wenn aber der Server nicht antwortet – vielleicht lieber eine andere Meldung anzeigen.
Hier kommen catch-Filter ins Spiel – ein echtes C#-Superfeature, mit dem du nicht nur nach Typ, sondern auch nach Zusatzbedingungen filtern kannst.
Syntax des when-Filters
catch (IOException ex) when (ex.Message.Contains("kein Laufwerk"))
{
Console.WriteLine("Ups! Sieht so aus, als hättest du den USB-Stick rausgezogen.");
}
catch (IOException ex)
{
Console.WriteLine("Anderer Ein-/Ausgabefehler: " + ex.Message);
}
Hier fängt der erste Block nur die IOException, deren Message die Phrase "kein Laufwerk" enthält – alles andere geht in den zweiten Block.
Einsatz im echten Leben
Filter sind besonders nützlich bei Netzwerkfehlern, wenn du je nach inneren Eigenschaften der Exception entscheiden willst: Wiederholen oder nur Fehler anzeigen.
Noch ein Beispiel: Stell dir vor, wir haben eine Methode, die eine Datei parst, und wir wollen nicht einfach irgendeine FormatException abfangen, sondern speziell, wenn es um das Alter geht (zum Beispiel, wenn das Alter als "abc" statt als Zahl drinsteht).
catch (FormatException ex) when (ex.Message.Contains("Alter"))
{
Console.WriteLine("Fehler: Alter konnte nicht gelesen werden. Daten prüfen!");
}
catch (FormatException ex)
{
Console.WriteLine("Datenformat-Fehler: " + ex.Message);
}
Filter und Performance
Filter sind super praktisch, aber denk dran: Die Bedingung im Filter wird vor dem Eintritt in den catch-Block ausgewertet. Wenn sie nicht zutrifft, wird der Block gar nicht betreten.
Übrigens: Wenn der Filter selbst eine Exception wirft (zum Beispiel Division durch Null im when-Ausdruck), dann wird diese Exception von diesem catch-Block nie gefangen. Also aufpassen!
3. Kombination von mehreren catch-Blöcken und Filtern
Stell dir vor, du musst im Projekt nicht nur nach Exception-Typ, sondern auch nach internem Zustand unterscheiden. Zum Beispiel: Bei IOException willst du unterschiedlich reagieren, je nachdem ob es um Zugriffsrechte oder um fehlenden Speicherplatz geht.
try
{
File.AppendAllText("log.txt", "Neuer Eintrag\n");
}
catch (IOException ex) when (ex.Message.Contains("Kein Platz"))
{
Console.WriteLine("Fehler: Kein Speicherplatz mehr auf dem Laufwerk!");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("Fehler: Keine Berechtigung zum Schreiben. Starte als Admin.");
}
catch (IOException ex)
{
Console.WriteLine("Anderer Laufwerksfehler: " + ex.Message);
}
Hier nutzen wir sowohl Filter als auch die Trennung nach Fehlertypen. Diese Flexibilität ist besonders wertvoll in komplexen Anwendungen, wo du auf häufige Fehler möglichst informativ reagieren willst.
4. Besonderheiten und "Fallen" bei Filtern und mehreren catch-Blöcken
- Der catch-Filter kann die Exception-Variable (ex) verwenden, aber nicht verändern.
- Wenn du im when eine Exception wirfst, wird sie von diesem catch nie gefangen – sie geht einfach weiter den Stack hoch, als ob es den Filter nicht gäbe.
- Die Filter-Logik ist Teil des "Vertrags": Dein Kollege im Projekt muss wissen, dass nicht jede IOException gefangen wird – nur wenn die Bedingung erfüllt ist.
- Wenn du nur einen catch für die ganze Methode hast, aber darin einen Filter, kann es sein, dass "übrige" Exceptions gar nicht gefangen werden. Im Zweifel lieber mehrere, explizite Blöcke nutzen.
- In großen Anwendungen kannst du Filter für zentrale Logik nutzen, z.B. Logging nur für kritische Fehler.
5. Szenarien für Vorstellungsgespräche und echte Arbeit
Das Wissen über Filter und mehrere catch-Blöcke ist nicht nur für den "schönen Code" oder einen Clean-Code-Bonus. Das ist eine echte Fähigkeit, die bei Vorstellungsgesprächen gefragt ist – vor allem, wenn du mit großen, komplexen, verteilten Anwendungen zu tun hast.
Beispielfragen:
- Wie würdest du in C# verschiedene Fehler beim Datei-Lesen behandeln, damit für verschiedene Situationen (Datei fehlt, Laufwerk voll, Daten kaputt) jeweils eine andere Meldung ausgegeben wird?
- Wie vermeidest du es, einen wichtigen Fehler zu "verschlucken", wenn du einen allgemeinen catch (Exception)-Block gesetzt hast?
- Wozu braucht man den catch-Filter? Kann man darin ein throw machen?
GO TO FULL VERSION