1. Einführung in den finally-Block
Aufräumen und Ressourcen freigeben
Stell dir vor: Du experimentierst in einem Chemielabor und wenn du fertig bist (oder sogar ein paar Kolben explodiert sind), musst du trotzdem deinen Arbeitsplatz aufräumen, Chemikalien abwaschen und das Licht ausschalten. Genau dafür gibt es den finally-Block – er wird immer ausgeführt, nach try und catch, egal ob eine Exception aufgetreten ist oder nicht.
try
{
// Hier kommt "gefährlicher" Code, der eine Exception auslösen kann
}
catch
{
// Hier fangen und behandeln wir Exceptions
}
finally
{
// Dieser Code wird immer ausgeführt, egal ob eine Exception aufgetreten ist oder nicht
}
Wofür braucht man das? In erster Linie, um Ressourcen garantiert freizugeben: Datei schließen, Datenbankverbindung beenden, Tür zum Serverraum entsperren... Ohne finally könnten nach einem Fehler manche Ressourcen "hängen bleiben" – und das ist ein echtes Problem. Zum Beispiel bleibt eine Datei gesperrt und selbst der Admin kann sie nicht öffnen.
Warum nicht alles in catch schreiben?
Könnten wir die Ressource nicht einfach im catch freigeben? Theoretisch ja. Aber wenn alles gut läuft, wird catch nicht ausgeführt. Wenn du sicherstellen willst, dass die Ressource immer freigegeben wird, brauchst du finally.
Im echten Leben gibt es oft Aufgaben, bei denen es egal ist, ob Erfolg oder Misserfolg – aufgeräumt werden muss immer. Genau dafür wurde finally erfunden.
2. Besonderheiten des finally-Blocks
Wann wird finally NICHT ausgeführt?
Fangfrage! finally wird immer ausgeführt. Selbst wenn im try-Block ein return (früher Ausstieg aus der Methode) steht oder eine neue Exception geworfen wird, finally wird trotzdem ausgeführt.
static void Test()
{
try
{
Console.WriteLine("Vor return");
return;
}
finally
{
Console.WriteLine("finally wird trotzdem ausgeführt!");
}
}
//Aufruf von Test()
Test();
Gibt aus:
// Vor return
// finally wird trotzdem ausgeführt!
Allerdings, wenn deine Anwendung plötzlich "hart abstürzt" (z.B. Computer wird ausgeschaltet, Prozess wird gekillt oder die ganze CLR stirbt), wird der finally-Block nicht ausgeführt. Da kann man dann aber auch nichts machen.
finally-Operator und Call Stack
Den Call Stack schauen wir uns in der nächsten Vorlesung genauer an, aber kurz gesagt: Das ist wie ein Stapel von aufgerufenen Methoden, durch den das Programm "runtergeht", wenn kein passender catch gefunden wird.
Noch ein wichtiger Punkt: Wenn im try eine Exception auftritt, aber im catch nicht abgefangen wird (z.B. kein passender Handler), verlässt das Programm die aktuelle Methode und geht weiter den Call Stack hoch, bis ein passender catch gefunden wird. Aber vorher wird auf jedem Level der finally-Block ausgeführt.
3. Der throw-Operator: Wie man selbst Exceptions wirft
Was ist throw und wofür braucht man ihn?
Manchmal reicht es nicht, eine Exception nur abzufangen – manchmal muss man einen eigenen Fehler erzeugen und "rauswerfen". Genau dafür gibt es den throw-Operator.
throw new Exception("Das ist mein spezieller Fehler!");
throw sagt der CLR quasi: "Ich habe hier was richtig Schlimmes entdeckt, ich werfe meine Exception, jetzt soll sich der kümmern, der diesen Code aufgerufen hat."
throw ohne neue Exception zu erzeugen
Du kannst throw; im catch-Block benutzen, um die gerade gefangene Exception erneut zu werfen – zum Beispiel, wenn du einen Teil des Fehlers behandelt hast, aber die Verantwortung für die weitere Behandlung an den aufrufenden Code weitergeben willst.
try
{
DangerousOperation();
}
catch (Exception ex)
{
LogError(ex);
throw; // wirft die aktuelle Exception erneut, Call Stack bleibt erhalten
}
Wenn wir throw ex; geschrieben hätten, wäre die Call Stack-Info verloren gegangen – das ist schlechte Praxis.
4. Wie funktioniert finally zusammen mit throw?
finally wird auch bei geworfener Exception ausgeführt
Lass uns prüfen, was passiert, wenn im try ein throw passiert, aber wir auch einen finally haben:
try
{
Console.WriteLine("Vor dem Fehler...");
throw new Exception("Fehler während try!");
}
catch
{
Console.WriteLine("Catch fängt den Fehler.");
}
finally
{
Console.WriteLine("Finally wurde ausgeführt.");
}
Ergebnis:
Vor dem Fehler...
Catch fängt den Fehler.
Finally wurde ausgeführt.
Und wenn es keinen passenden catch gibt, wird finally trotzdem ausgeführt, bevor das Programm abstürzt.
Feinheiten: Was passiert, wenn in finally auch ein throw steht?
Wenn im finally ein throw passiert, dann ersetzt diese Exception die vorherige. Das heißt, die Info darüber, was im try/catch passiert ist, geht verloren. Deshalb sollte man im finally keinen throw werfen, wenn vorher schon eine Exception war.
try
{
throw new Exception("Fehler in try");
}
finally
{
throw new Exception("Fehler in finally");
}
// Ergebnis: Nach außen geht "Fehler in finally"
5. Praktische Tipps und typische Fehler
Was Anfänger oft vergessen
- Den finally-Block beim Freigeben von Ressourcen nicht zu verwenden und sich nur auf catch zu verlassen.
- Code, der neue Exceptions werfen kann, in den finally-Block zu packen – das führt zu unerwarteten Fehlern.
- Zu vergessen, dass return im try den finally-Block nicht überspringt – der wird immer ausgeführt.
Alternative zu finally: Was soll man wählen?
Mit der Einführung der using-Konstruktion (die wir später noch genauer anschauen), ist das Freigeben von Ressourcen noch bequemer geworden, aber im Grunde benutzt using unter der Haube auch nur finally. Für alle Sonderfälle (z.B. Entsperren oder Senden einer Fehlermeldung) muss man trotzdem finally verwenden.
Warum finally bei Vorstellungsgesprächen so beliebt ist
Jeder Recruiter, der mal einen Server unter hoher Last geschrieben hat, stellt gerne Fragen zu finally. Typische Fragen sind: "Was passiert, wenn im try ein return steht und im finally ein throw?" oder "Werden Ressourcen bei Exceptions garantiert freigegeben?". Und jetzt hast du nicht nur die Antworten, sondern auch das Verständnis. Du kannst nicht nur erklären, wie finally funktioniert, sondern auch, warum man es braucht, wann man es verwendet und warum kein ernsthafter Code ohne auskommt.
GO TO FULL VERSION