Werfen und eigene Exceptions
Kurz gesagt: Mit throw lehnt eine Methode ab und wirft eine Exception, die durch die Aufrufe fällt, bis ein catch sie fängt; für deinen eigenen Fall schreibst du eine eigene Exception-Klasse.
Problem
Der Geldautomat sagt „Nimm dein Geld“
Das Konto speichert seinen Kontostand. Wenn der Betrag zum Abheben negativ ist oder das Geld nicht reicht, tut die Methode einfach nichts:
public void Withdraw(int amount){ if (amount > 0 && amount <= Balance) { Balance -= amount; }}Der Geldautomat benutzt sie so:
Account account = new Account(100);account.Withdraw(500);Console.WriteLine("Nimm dein Geld.");Console.WriteLine("Kontostand: " + account.Balance + " €");Nimm dein Geld.Kontostand: 100 €Der Kunde will 500 € und hat 100 €. Die Methode lehnt still ab, der Geldautomat merkt nichts und sagt „Nimm dein Geld.“ Der Fehler geht verloren. Die Methode könnte true oder false zurückgeben, aber der Aufrufer vergisst leicht, das zu prüfen, und false sagt nicht, warum.
Die Idee
throw: Ich lehne ab und sage, warum
Statt zu schweigen, wirft die Methode eine Exception:
public void Withdraw(int amount){ if (amount <= 0) { throw new ArgumentException("Der Betrag muss positiv sein"); } if (amount > Balance) { throw new ArgumentException("Nicht genug Geld"); } Balance -= amount;}throw beendet die Methode wie return. Die Zeile Balance -= amount wird nie erreicht. Aber statt eines Werts schickt die Methode eine Exception, und der Aufrufer kann sie nicht übersehen:
Account account = new Account(100);try{ account.Withdraw(500); Console.WriteLine("Nimm dein Geld.");}catch (ArgumentException e){ Console.WriteLine("Abgelehnt: " + e.Message);}Console.WriteLine("Kontostand: " + account.Balance + " €");Abgelehnt: Nicht genug GeldKontostand: 100 €newerzeugt das Exception-Objekt wie jedes andere Objekt. Die Meldung kommt in seinen Konstruktor.ArgumentExceptionist die fertige Exception für „das Argument ist falsch“. Du benutzt sie genau bei solchen Prüfungen.
Genauer, für Neugierige
ArgumentOutOfRangeExceptionist für eine Zahl außerhalb der erlaubten Grenzen, undInvalidOperationExceptionfür ein Objekt, das nicht im richtigen Zustand für diese Aktion ist, etwa beim Abheben von einem geschlossenen Konto.throwfunktioniert nur mit einem Objekt einer Unterklasse vonException.
Sieh es dir an
Die Exception fällt durch
public void Withdraw(int amount){ if (amount > Balance) { throw new ArgumentException("Nicht genug Geld"); } Balance -= amount;}static void PayRent(Account account){ account.Withdraw(800); Console.WriteLine("Die Miete ist bezahlt.");}
try{ PayRent(new Account(500));}catch (ArgumentException e){ Console.WriteLine("Abgelehnt: " + e.Message);}Schritt für Schritt, in Worten
- Main ruft PayRent für ein Konto mit 500 € auf.
- PayRent ruft Withdraw(800) auf: ein neuer Frame oben drauf.
- 800 > 500: Die Methode wirft eine Exception.
- Der Kontostand ändert sich nicht: Der Frame wird verworfen, ohne seine Zeilen zu Ende auszuführen.
- PayRent hat kein catch: „Die Miete ist bezahlt.“ wird nicht ausgegeben, und der Frame wird verworfen.
- Das catch in Main fängt die Exception, und das Programm macht hier weiter.
Erinnere dich an den Aufrufstapel aus der Lektion über Rekursion. Main ruft PayRent auf, und diese Methode ruft Withdraw auf. Die Frames liegen übereinander.
Wenn Withdraw wirft, sucht die Exception ein catch, indem sie den Stapel hinuntergeht. Ein Frame ohne passendes catch wird sofort verworfen, ohne seine Zeilen zu Ende auszuführen. So geht es auch PayRent. Erst Main hat ein catch, und das Programm macht dort weiter.
Hätte auch Main kein catch, würde auch Main verworfen. Dann hält das Programm an und gibt einen Stacktrace aus: genau diese Frames, von innen nach außen.
Probier es aus
Was erscheint auf dem Bildschirm?
Das Konto hat 50 €, und die Bestellung kostet 120 €.
Was gibt das Programm aus?
static void Checkout(Account account){ Console.WriteLine("Ich bezahle die Bestellung"); account.Withdraw(120); Console.WriteLine("Die Bestellung ist bezahlt");}
Account account = new Account(50);try{ Checkout(account); Console.WriteLine("Ich sende die Bestätigung");}catch (ArgumentException e){ Console.WriteLine("Abgelehnt: " + e.Message);}Console.WriteLine("Kontostand: " + account.Balance + " €");„Ich bezahle die Bestellung“ kommt vor dem Abheben. Dann wirft Withdraw, und der Frame von Checkout wird verworfen: „Die Bestellung ist bezahlt“ erscheint nie. In Main wird auch „Ich sende die Bestätigung“ übersprungen, weil es im try nach dem Aufruf steht. catch gibt die Ablehnung aus, und der Kontostand bleibt unberührt.
Probier es aus
Finde den Fehler
Die Miete beträgt 800 €, und auf dem Konto sind 500 €. Das Programm sagt „Die Miete ist bezahlt.“ In welcher Zeile ist der Fehler?
Wähle die Zeile mit dem Fehler.
catch fängt die Exception und tut nichts. Der Fehler verschwindet, genau wie beim Geldautomaten in der ersten Karte. Fangen sollst du dort, wo du etwas Sinnvolles tun kannst. Hier kann PayRent das Geld nicht herbeischaffen, also sollte die Methode nicht fangen. Entferne try/catch: Die Exception erreicht dann Main, und dort erfährt die Person davon.Genauer, für Neugierige
- Manchmal fängt eine Methode, tut etwas, etwa einen Eintrag ins Log schreiben, und wirft dieselbe Exception weiter:
throw;imcatch. Nurthrow;ohne Namen behält den ursprünglichen Stacktrace.throw e;beginnt ihn ab dieser Zeile neu. - Du kannst auch eine andere Exception werfen, die besser sagt, was passiert ist. Dann gib die alte als zweites Argument an den Konstruktor:
new InvalidOperationException("Die Miete ist nicht bezahlt", e). Im Stacktrace siehst du dann beide.
Die Idee
Eine eigene Exception
„Nicht genug Geld“ ist Text. Wenn der Geldautomat „Zahle noch 30 € ein“ vorschlagen will, muss er wissen, wie viel fehlt. Die Zahl aus dem Text herauszuziehen ist zerbrechlich. Schreib lieber eine eigene Exception-Klasse mit einem Feld:
class InsufficientFundsException : Exception{ public int Shortfall { get; }
public InsufficientFundsException(int shortfall) : base("Es fehlen " + shortfall + " €") { Shortfall = shortfall; }}Deine eigene Exception ist eine gewöhnliche Klasse, die von Exception erbt. Der Konstruktor gibt die Meldung mit : base(...) an die Basisklasse weiter. Die Methode wirft sie, wenn das Geld nicht reicht:
public void Withdraw(int amount){ if (amount <= 0) { throw new ArgumentException("Der Betrag muss positiv sein"); } if (amount > Balance) { throw new InsufficientFundsException(amount - Balance); } Balance -= amount;}Und der Aufrufer fängt die beiden Fälle getrennt:
Account account = new Account(100);try{ account.Withdraw(130);}catch (InsufficientFundsException e){ Console.WriteLine(e.Message); Console.WriteLine("Zahle noch " + e.Shortfall + " € ein oder wähle einen kleineren Betrag.");}catch (ArgumentException e){ Console.WriteLine("Abgelehnt: " + e.Message);}Es fehlen 30 €Zahle noch 30 € ein oder wähle einen kleineren Betrag.Genauer, für Neugierige
- Namen von Exceptions enden immer auf
Exception. - Deine eigene Exception kann auch von einer spezifischeren fertigen Klasse erben, etwa
ArgumentException. Dann fängt eincatchfür die Basisklasse auch sie. Hier ist sie getrennt, deshalb spielt die Reihenfolge der beidencatch-Blöcke keine Rolle.
Die Idee
Eigene oder fertige?
In C# erbt deine eigene Exception von Exception, und das war's: Checked Exceptions gibt es nicht. Die Frage ist eine andere: Brauchst du überhaupt eine eigene?
Eine fertige Exception, wenn die Bedeutung passt: ArgumentException für ein falsches Argument, InvalidOperationException für eine Aktion im falschen Moment. Eine eigene, wenn der Aufrufer sie von den anderen unterscheiden und auf seine eigene Weise reagieren muss oder wenn die Exception Daten tragen muss, wie shortfall.
Probier es aus
Fülle die Lücke
Eine Exception für ein ungültiges Alter. Was gehört in die Lücke, damit die Meldung bei der Basisklasse ankommt?
class InvalidAgeException : Exception
{
public InvalidAgeException(string message) : (message)
{
}
}: base(message) ruft den Konstruktor der Basisklasse auf. Die Basisklasse speichert die Meldung, und Message gibt sie zurück.
Übung
Exceptions in Klassen
Löse die Aufgaben in deinem Editor.
1. Musikgeschäft. Das Geschäft führt Instrumente vor, aber nicht jedes lässt sich vorführen.
- Eine abstrakte Klasse
MusicalInstrumentmit Name, Marke und Preis (schreibgeschützte Properties) und einem Konstruktor. - Ein Interface
IPlayablemit einer MethodePlay(). GuitarundPianoerben vom Instrument und können spielen.Drumerbt nur.NotPlayableException : Exception.MusicShopmit einer MethodeDemoInstrument(MusicalInstrument instrument): Wenn das Instrument spielen kann (is IPlayable playable), führt sie es vor, sonst wirft sieNotPlayableException.
- Führe in Main eine Gitarre Fender Stratocaster (1299 €), ein Schlagzeug Pearl Export (849 €) und ein Klavier Yamaha P-45 (499 €) aus einem Array vor. Fange die Exception für jedes Instrument einzeln, damit die Vorführung weitergeht.
Fender Stratocaster, 1299.00 €Die Gitarre spielt einen Akkord.Pearl Export, 849.00 €Pearl Export kann nicht vorgeführt werden.Yamaha P-45, 499.00 €Das Klavier spielt eine Tonleiter.2. Ein Konto mit Regeln. Füge Account eine Methode Deposit(int amount) hinzu, die bei einem Betrag von 0 oder weniger ArgumentException wirft. Das Abheben wirft InsufficientFundsException aus der Lektion. Das Konto startet mit 100 €. Lies die Anzahl der Operationen und dann die Operationen selbst, jede in einer eigenen Zeile: „einzahlen 50“ oder „abheben 500“. Gib nach jeder Operation den Kontostand aus oder warum sie abgelehnt wurde.
4einzahlen 50abheben 500abheben -5abheben 120Kontostand: 150 €Es fehlen 350 €. Kontostand: 150 €Der Betrag muss positiv sein. Kontostand: 150 €Kontostand: 30 €3. Ungültiges Alter. Person hat einen Namen und ein Alter. Die Property Age wirft InvalidAgeException aus der vorigen Karte, wenn das Alter unter 0 oder über 150 liegt. Auch der Konstruktor geht über Age, damit niemand eine Person mit ungültigem Alter erzeugen kann. Erzeuge Maria (19), Peter (−3) und Ivan (200).
Maria, 19 JahrePeter: Ungültiges Alter: -3Ivan: Ungültiges Alter: 200Weitere Aufgaben
- Tischreservierung. Eine Klasse
Restaurantmit einer Anzahl freier Tische und einer Methode zum Reservieren. Wenn keine Tische mehr frei sind, wirf deine eigeneNoTablesException. Wenn die Zahl der Gäste 0 oder kleiner ist, wirf die fertige Exception für ein falsches Argument. - Ein Produkt mit Lagerbestand. Eine Klasse
Productmit einem Lagerbestand und einer MethodeSell(int qty). Wenn der Bestand nicht reicht, wirfOutOfStockException, die mitträgt, wie viele Stück da sind. Der Shop fängt die Exception und bietet an, die vorhandenen zu verkaufen. - Musikgeschäft mit Grund. Füge
NotPlayableExceptionein Feld mit dem Instrument hinzu, das nicht spielen kann, und gib imcatchseinen Preis aus.
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Interfaces · Typprüfungen · Exceptions · try, catch und finally
- Geht weiter in: record, switch und Muster · Entwurfsmuster · Tests mit xUnit · Datenbanken
- Aufgaben zum Thema: Fehler und Exceptions