Zum Inhalt
wannacode

try, catch und finally

Kurz gesagt: Mit mehreren catch-Blöcken fängst du verschiedene Arten von Exceptions, vom Speziellen zum Allgemeinen, und finally läuft immer, mit oder ohne Fehler.

Problem

Die Rechnung für null Personen

Das Programm teilt die Restaurantrechnung gleichmäßig auf. Falsche Eingaben kennst du schon, und du fängst sie:

Program.cs
class Program
{
static void Main()
{
try
{
int bill = int.Parse(Console.ReadLine());
int people = int.Parse(Console.ReadLine());
Console.WriteLine("Pro Person: " + bill / people + " €");
}
catch (FormatException)
{
Console.WriteLine("Gib ganze Zahlen ein.");
}
}
}

Die Rechnung beträgt 90 €, und jemand gibt 0 als Zahl der Personen ein. Das Programm hält wieder an:

Fehler
Unhandled exception. System.DivideByZeroException: Attempted to divide by zero.
at Program.Main() in /projects/Bill/Program.cs:line 9

catch ist da, hilft aber nicht. Es fängt nur FormatException, und das hier ist eine andere Art von Exception.

Die Idee

Mehrere catch-Blöcke

Auf ein try können mehrere catch-Blöcke folgen, einer für jede Art von Fehler:

try
{
int bill = int.Parse(Console.ReadLine());
int people = int.Parse(Console.ReadLine());
Console.WriteLine("Pro Person: " + bill / people + " €");
}
catch (FormatException)
{
Console.WriteLine("Gib ganze Zahlen ein.");
}
catch (DivideByZeroException)
{
Console.WriteLine("Es können nicht 0 Personen sein.");
}
Eingabe Ausgabe
90 und 3 Pro Person: 30 €
90 und 0 Es können nicht 0 Personen sein.
90 und drei Gib ganze Zahlen ein.

Die Blöcke werden von oben nach unten geprüft. Der erste, dessen Art passt, läuft, und nur dieser. Die übrigen werden übersprungen.

try
{
int bill = int.Parse(Console.ReadLine());
int people = int.Parse(Console.ReadLine());
Console.WriteLine("Pro Person: " + bill / people + " €");
}
catch (FormatException)
{
Console.WriteLine("Gib ganze Zahlen ein.");
}
catch (DivideByZeroException)
{
Console.WriteLine("Es können nicht 0 Personen sein.");
}
Schritt für Schritt, in Worten
  1. Erster Lauf: Die Rechnung beträgt 90 €, und es sind 0 Personen.
  2. 90 / 0 ist eine Division durch null: DivideByZeroException wird geworfen.
  3. Das erste catch ist für FormatException. Es passt nicht und wird übersprungen.
  4. Das zweite passt. Nur dieses läuft.
  5. Zweiter Lauf: Statt der Zahl der Personen wird „drei“ eingegeben.
  6. int.Parse kann keine Zahl machen: FormatException wird geworfen.
  7. Schon das erste catch passt. Das zweite wird nie erreicht.
  8. Die Blöcke werden von oben nach unten geprüft. Der erste, der passt, läuft.
Genauer, für Neugierige
  • catch kann auch eine Bedingung haben: catch (FormatException) when (text == ""). Der Block fängt nur, wenn die Bedingung wahr ist.

Die Idee

Die Familie der Exceptions

Exceptions sind Klassen, die durch Vererbung verbunden sind. Hier sind die häufigsten:

Die Hierarchie
Exception
├── SystemException
│ ├── ArithmeticException
│ │ ├── DivideByZeroException
│ │ └── OverflowException
│ ├── FormatException
│ ├── IndexOutOfRangeException
│ ├── NullReferenceException
│ ├── ArgumentException
│ │ ├── ArgumentNullException
│ │ └── ArgumentOutOfRangeException
│ └── IOException Dateien, Netzwerk
└── ApplicationException

catch fängt die Art, die du angibst, und alle ihre Unterklassen. Das ist die Polymorphie aus dem Modul über Vererbung: Eine Variable vom Typ der Basisklasse kann auf ein Objekt einer Unterklasse zeigen. Deshalb fängt catch (Exception e) alles:

static void Split(string billText, string peopleText)
{
try
{
int bill = int.Parse(billText);
int people = int.Parse(peopleText);
Console.WriteLine("Pro Person: " + bill / people + " €");
}
catch (Exception e)
{
Console.WriteLine(e.GetType().Name + ": " + e.Message);
}
}
Split("90", "3");
Split("90", "0");
Split("90", "drei");
Ausgabe
Pro Person: 30 €
DivideByZeroException: Attempted to divide by zero.
FormatException: The input string 'drei' was not in a correct format.

e.GetType().Name gibt den Namen der echten Klasse des Objekts. Die Variable ist vom Typ Exception, aber das Objekt ist von einer Unterklasse.

Genauer, für Neugierige
  • StackOverflowException kann man überhaupt nicht fangen: .NET hält das Programm sofort an. OutOfMemoryException kann man fangen, aber es ist selten sinnvoll.
  • ApplicationException war als Basisklasse der Exceptions gedacht, die Programme selbst definieren. In der Praxis leitest du eigene Exceptions direkt von Exception ab.
  • Fange so speziell, wie du kannst. catch (Exception e) versteckt auch Fehler, die du nicht erwartet hast, etwa einen Bug in deinem eigenen Code.

Probier es aus

Welches catch?

Was gibt das Programm aus?

int[] seats = { 4, 2, 6 };
try
{
Console.WriteLine("Tisch 4: " + seats[3] + " Plätze");
}
catch (ArithmeticException)
{
Console.WriteLine("Division durch null");
}
catch (SystemException)
{
Console.WriteLine("Laufzeitfehler");
}
catch (Exception)
{
Console.WriteLine("Allgemeiner Fehler");
}
Antwort

Probier es aus

Finde den Fehler

Dieses Programm lässt sich nicht kompilieren. In welcher Zeile steckt der Fehler?

Wähle die Zeile mit dem Fehler.

Die Idee

Der Compiler zwingt dich nicht

Java hat Checked Exceptions: Der Compiler akzeptiert das Programm erst, wenn du sie fängst oder deklarierst. C# hat keine solchen. Keine Exception muss gefangen werden:

static void PrintReceipt()
{
Console.WriteLine("Drucke den Kassenbon...");
Thread.Sleep(300);
Console.WriteLine("Fertig.");
}
PrintReceipt();
Ausgabe
Drucke den Kassenbon...
Fertig.

Thread.Sleep hält das Programm 300 Millisekunden an und kann eine Exception werfen, aber der Compiler verlangt nichts. Dasselbe gilt für int.Parse.

Die Verantwortung liegt also bei dir. Welche Exceptions eine Methode wirft, steht in ihrer Dokumentation, im Abschnitt „Exceptions“. Lies ihn, bevor du entscheidest, ob du fängst.

Die Idee

finally: immer

Der Drucker an der Kasse muss nach jedem Kassenbon freigegeben werden, sonst wird der nächste nicht gedruckt. Egal, was passiert, Erfolg oder Fehler:

static void PrintReceipt(string qtyText)
{
Console.WriteLine("Der Drucker ist belegt.");
try
{
int qty = int.Parse(qtyText);
Console.WriteLine("Kaffee: " + qty + " x 2 € = " + qty * 2 + " €");
}
catch (FormatException)
{
Console.WriteLine("Falsche Menge: " + qtyText);
}
finally
{
Console.WriteLine("Der Drucker ist frei.");
}
}
PrintReceipt("3");
PrintReceipt("drei");
Ausgabe
Der Drucker ist belegt.
Kaffee: 3 x 2 € = 6 €
Der Drucker ist frei.
Der Drucker ist belegt.
Falsche Menge: drei
Der Drucker ist frei.

finally läuft immer: wenn alles gut geht, wenn die Exception gefangen wird, und sogar wenn keiner dieser catch-Blöcke sie fängt. Im letzten Fall läuft finally, und die Exception geht weiter nach oben zum nächsten try, das sie fangen kann.

Warum nicht einfach eine Zeile nach dem try/catch? Weil diese Zeile übersprungen wird, wenn die Exception von einer Art ist, die catch nicht fängt, oder wenn im try ein return steht. finally wird nicht übersprungen.

Genauer, für Neugierige
  • Es geht auch try mit finally und ohne catch: Die Exception wird nicht gefangen, aber das Aufräumen läuft.
  • Für Ressourcen, die geschlossen werden müssen, etwa Dateien und Datenbankverbindungen, gibt es eine kürzere Form: using. Du triffst es, wenn du mit Dateien und Datenbanken arbeitest. Diese Form schließt die Ressource von selbst, so wie finally es tun würde.
  • finally läuft nur dann nicht, wenn das ganze Programm angehalten wird, etwa mit Environment.Exit(0).
  • Wenn die Exception nirgends im Programm gefangen wird, kann .NET es anhalten, ohne die finally-Blöcke auszuführen. Fange die Exceptions deshalb wenigstens in Main, wenn in finally etwas Wichtiges steht.

Probier es aus

finally und return

Was gibt das Programm aus?

static string Reserve(int guests)
{
try
{
if (guests > 6)
{
return "Kein Tisch für " + guests;
}
return "Tisch für " + guests;
}
finally
{
Console.WriteLine("Versuch protokolliert.");
}
}
Console.WriteLine(Reserve(8));
Antwort

Übung

Programme mit mehreren catch-Blöcken

Löse die Aufgaben in deinem Editor. Gib keine Eingabeaufforderungen aus.

1. Preis pro Person. Die Preise stehen im Array { 120, 75, 300 }. Lies eine Produktnummer (ab 1) und die Zahl der Personen und gib den Preis pro Person mit Ganzzahldivision aus. Fange drei Dinge: ein Produkt, das es nicht gibt, null Personen und alles andere (dann gib die Meldung der Exception aus).

Eingabe Ausgabe
2 und 3 Pro Person: 25 €
5 und 2 Dieses Produkt gibt es nicht.
1 und 0 Die Zahl der Personen darf nicht 0 sein.
1 und zwei Fehler: The input string 'zwei' was not in a correct format.

2. Taschenrechner. Lies eine ganze Zahl, einen Operator (+, -, * oder /) und eine zweite ganze Zahl, jede in einer eigenen Zeile. Gib die Rechnung aus. Bei Text statt einer Zahl sag „Gib ganze Zahlen ein.“, bei Division durch null „Division durch null.“ und bei jedem anderen Operator „Unbekannter Operator: “ und dahinter den Operator.

Eingabe
84
/
4
Ausgabe
84 / 4 = 21
Eingabe
84
/
0
Ausgabe
Division durch null.

Weitere Aufgaben

  • Das Kartenterminal. Die Methode Pay(string amountText) gibt „Das Terminal ist verbunden.“ aus, dann „Bezahlt: X €“ oder eine Meldung über die falsche Eingabe, und im finally „Das Terminal ist aus.“ Rufe sie mit „25“ und mit „zwanzig“ auf.
  • Durchschnittsnote ohne Absturz. Lies 5 Zeilen mit Noten. Überspringe die, die keine ganzen Zahlen sind (eine Exception) oder nicht zwischen 2 und 6 liegen (ein if). Gib den Durchschnitt mit Ganzzahldivision aus. Wenn keine Note gültig ist, wirft die Division durch null: Fange sie und gib „Keine gültigen Noten.“ aus.
  • Vorhersagen und prüfen. Schreibe ein try mit catch und finally, in dem jeder Block eine Zeile ausgibt. Sage die Ausgabe bei Erfolg, bei einer gefangenen und bei einer nicht gefangenen Exception voraus, und führe es dann aus.

Test

Teste dich

1. Die Exception passt zu zwei der drei catch-Blöcke. Welcher läuft?
2. Fängt catch (SystemException e) eine Exception vom Typ FormatException?
3. Warum muss catch (Exception e) als letztes kommen?
4. Wann läuft finally?
5. Zwingt dich der C#-Compiler, FormatException um int.Parse herum zu fangen?

Erstellt von Ivo Gergov im Rahmen der Kampagne von Evo Tech für zugängliche Programmierausbildung ·