Arbeiten mit .NET: C-Sharp/ Arbeitsablauf/ Kontrollstrukturen/ Ausnahmen und Fehler
Achtung, Baustelle! Dieses Buch wird zurzeit überarbeitet und in Arbeiten mit .NET eingegliedert.
Hinweise für Leser: Der Inhalt eines Kapitels passt unter Umständen nicht richtig zum Namen oder zur Gliederung. Gliederung und Inhaltsverzeichnis des Buches können vom Inhalt einzelner Kapitel abweichen. Verweise auf andere Kapitel können falsch sein.
Hinweis für Autoren: Bitte berücksichtigen Sie bereits jetzt die Konzeption der Buchreihe.
Fehler sind menschlich, und 90% der Fehler sitzen vor dem Monitor. --- Wer wüsste das besser als Microsoft? ... Nein, das war ein Scherz. Aber es ist wirklich völlig normal, dass Benutzer nicht immer absolut diszipliniert dort Zahlen eintragen, wo wir Zahlen erwarten; oder dass sie sich mal bei der Eingabe einer E-Mail-Adresse vertippen. Und es ist ebenso möglich, dass beispielsweise eine Datei verschwunden ist, auf die wir gerade zugreifen wollen. ... Kurz gesagt, den größten Teil unserer Zeit in der Programmierung beschäftigen wir uns damit, uns mögliche Fehler auszudenken, die in unserem Programm auftauchen könnten --- und diese dann irgendwie zu "behandeln".
Deshalb ist eine ausgewogene Fehlerbehandlung ein absolutes Must Have für uns als Programmierer. Und weil diese Behandlung möglicher und unmöglicher, erwarteter und unerwarteter Fehler so unendlich viel Zeit frisst, haben uns die Entwickler des .NET-Frameworks ein umfangreiches Paket an Hilfsmitteln zur Verfügung gestellt.
In diesem Abschnitt beschäftigen wir uns mit den grundlegenden Techniken der Ausnahmebehandlung. Weiterführende Informationen befinden sich im Abschnitt Mehr Ausnahmen und Fehler.
Problem
BearbeitenWir wollen eine Eingabemaske erstellen, in der unser Benutzer seinen Namen, sein Alter und seine Email-Adresse eintragen soll. Normalerweise wäre diese Aufgabe nun wirklich nichts, was uns den Angstschweiß auf die Stirn treiben würde. Wir müssten nur soetwas ähnliches, wie das Folgende schreiben:
// Wir nehmen den Namen des Benutzers an Console.Write( "Name: " ); string name = Console.ReadLine(); // Wir nehmen das Alter des Benutzers an // Da die Eingabe als "String" erfolgt, // müssen wir sie casten. // Es reicht aus, einen Cast auf einen Short // zu machen, weil nur wenige Menschen älter // als 32.767 Jahre werden. Console.Write( "Alter: " ); string bufAlter = Console.ReadLine(); short alter = System.Convert.ToInt16( bufAlter ); // Wir nehmen die Email-Adresse des Benutzers an. Console.Write( "Email: " ); string email = Console.ReadLine(); // Wir geben die Daten auf dem Bildschirm aus. Console.WriteLine( "Hallo {0}, du bist {1} Jahre alt, und deine Email-Adresse lautet {2}.", name, alter, email );
Das funktioniert wunderbar --- bis der erste Spaßvogel als "Alter" beispielsweise "19a" einträgt. Dann stürzt unser ganzes Programm einfach ab, und wir bekommen eine Fehlermeldung zu sehen.
Fehler FormatException Die Eingabezeichenfolge hat das falsche Format.
Natürlich könnten wir jetzt eine Schulung anberaumen und die Benutzer auf Linie trimmen. "... und wehe, du gibst noch einmal einen Buchstaben beim Alter ein!". Wir können es aber auch viel geschickter machen. Wir lassen es einfach nicht zu, dass jemand hier falsche Werte eingeben kann und fragen so lange hartnäckig nach, bis der Benutzer den richtigen Wert einträgt.
Try-Catch
BearbeitenIn einem try-Block teilen wir dem Programm mit, dass wir etwas versuchen möchten, das möglicherweise schief gehen könnte. Und falls es in die Hose geht, soll das Programm bitte den catch-Block ausführen, in dem wir versuchen werden, den Fehler zu korrigieren --- oder wenigstens das Programm ordentlich zu beenden, ohne dass wichtige Daten verloren gehen können. Korrigieren wir dazu die folgenden Zeilen,
// Wir nehmen das Alter des Benutzers an Console.Write( "Alter: " ); string bufAlter = Console.ReadLine(); int alter = System.Convert.ToInt16( bufAlter );
sodass sie anschließend so aussehen:
// Wir nehmen das Alter des Benutzers an short alter = 0; Console.Write( "Alter: " ); string bufAlter = Console.ReadLine(); try // ... der kritische Abschnitt { alter = System.Convert.ToInt16( bufAlter ); } catch // ... Was machen wir, wenn es schief geht? { Console.WriteLine( "Bitte geben Sie nur Zahlen ein!" ); }
Wenn wir das jetzt testen, stellen wir fest, dass es wunderbar funktioniert. Der Fehler ist weg, das Programm läuft weiter. Allerdings hat es nun einen anderen Fehler: Der Benutzer kann seinen Irrtum (wir gehen davon aus, dass er das nicht absichtlich getan hat) nicht berichtigen. Das Programm macht einfach weiter und sieht nicht ein, dass der Benutzer sich geirrt hat und es gern korrigieren möchte. In der Ausgabe steht dann, dass der Benutzer "0 Jahre" alt ist. Aber auch das können wir ganz leicht beheben. Wir bauen die Eingabe des Alters einfach in eine Schleife, die so lange durchlaufen wird, wie das Alter "0 Jahre" beträgt.
// Wir nehmen das Alter des Benutzers an short alter = 0; do { Console.Write( "Alter: " ); string bufAlter = Console.ReadLine(); try // ... der kritische Abschnitt { alter = System.Convert.ToInt16( bufAlter ); } catch // ... Was machen wir, wenn es schief geht? { Console.WriteLine( "Bitte geben Sie nur Zahlen ein!" ); } } while ( alter == 0 );
Unser ganzes Programm sieht also jetzt so aus:
using System; namespace Org.Wikibooks.De.Csharp.Test { class TryCatchTest { static void Main(string[] args) { // Wir nehmen den Namen des Benutzers an Console.Write( "Name: " ); string name = Console.ReadLine(); // Wir nehmen das Alter des Benutzers an // Da die Eingabe als "String" erfolgt, // müssen wir sie casten. // Es reicht aus, einen Cast auf einen Short // zu machen, weil nur wenige Menschen älter // als 32.767 Jahre werden. short alter = 0; do { Console.Write( "Alter: " ); string bufAlter = Console.ReadLine(); try // ... der kritische Abschnitt { alter = System.Convert.ToInt16( bufAlter ); } catch // ... Was machen wir, wenn es schief geht? { Console.WriteLine( "Bitte geben Sie nur Zahlen ein!" ); } } while ( alter == 0 ); // Wir nehmen die Email-Adresse des Benutzers an. Console.Write( "Email: " ); string email = Console.ReadLine(); // Wir geben die Daten auf dem Bildschirm aus. Console.WriteLine( "Hallo {0}, du bist {1} Jahre alt, und deine Email-Adresse lautet {2}.", name, alter, email ); } } }
Praxisbeispiele
BearbeitenEmail-Adresse überprüfen
BearbeitenOben haben wir zwar gelernt, wie man vermeidet, dass der Benutzer ein falsches Alter einträgt, aber die Email-Adresse haben wir stillschweigend übergangen. Das holen wir jetzt nach. Und schon haben wir das erste Problem. Wie überprüft man eine Email-Adresse überhaupt? Es gibt doch so unendlich viele unterschiedliche Schreibweisen! --- Um genau zu sein: eigentlich nicht! All diese verschiedenen Schreibweisen haben einige Dinge gemeinsam. Und die können wir durchaus überprüfen. Beispielsweise muss eine gültige Email-Adresse zwingend das @-Zeichen haben. Und dieses Zeichen darf nicht an der ersten Stelle stehen. Außerdem muss eine gültige Email-Adresse nach dem @-Zeichen mindestens einen Punkt (.) haben, der die sogenannte "Top-Level-Domain" von der eigentlichen "Domain" trennt.
Beispiel: info@wikibooks.de Top-Level-Domain = de Domain = wikibooks
Es gibt also eine ganze Anzahl Erkennungsmerkmale, die wir nutzen können. Okay, dann verbessern wir unsere Email-Abfrage doch gleich:
// Wir nehmen die Email-Adresse des Benutzers an. bool emailIstOkay = false; string email = ""; do { Console.Write( "Email: " ); email = Console.ReadLine(); if ((email.Contains("@")) // Abfrage: Haben wir ein @-Zeichen? && (email[0] != '@') // Und steht das weder am Anfang, ... && (email[email.Length - 1] != '@')) // ... noch am Ende? { emailIstOkay = true;
// Hier kommt die weitere Suche nach // bekannten Mustern in der Email-Adresse... } } while ( !emailIstOkay );
Stopp! Das wird zu kompliziert. Genau für solche Zwecke, also wenn wir komplizierte Vergleiche machen müssen, die dutzende Programmcode-Zeilen erfordern würden, haben uns die Entwickler des .NET-Frameworks wieder etwas in den Werkzeugkasten gelegt: die Regex-Klasse. Sie ist darauf spezialisiert, solche Vergleiche extrem zu verkürzen. Wir haben also die Wahl: Entweder wir schreiben uns einen Wolf bei der Überprüfung einer einfachen Email-Adresse, oder wir nehmen die Regex-Klasse. Dazu fügen wir am Kopf unseres Programms zunächst einen weiteren Verweis auf das Paket, in dem sich diese Regex-Klasse befindet, hinzu:
using System; // Das sollte schon dastehen. using System.Text.RegularExpressions; // Das ist neu.
Jetzt können wir unsere Email überprüfen:
// Wir nehmen die Email-Adresse des Benutzers an. bool emailIstOkay = false; string email = ""; do { Console.Write( "Email: " ); email = Console.ReadLine(); try { // So sieht ein einfaches Muster aus // mit dem man Emails überprüfen kann. // Es findet nicht alle möglichen Varianten, // kann aber die meisten gebräuchlichen erkennen. string suchMuster = @"\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*"; // Wir prüfen die Email-Adresse mit Hilfe der RegEx-Klasse. // Dazu fragen wir die RegEx-Klasse, // ob die Email-Adresse das Suchmuster "matcht", // also ob es passt. // Wenn ja, haben wir eine gültige Email-Adresse. emailIstOkay = Regex.Match( email, suchMuster ).Success; } catch { Console.WriteLine ( "Die angegebene Email-Adresse ist ungültig!" ); } } while ( !emailIstOkay );
Unser vollständiges Programm sieht nun also so aus:
using System; using System.Text.RegularExpressions; namespace Org.Wikibooks.De.Csharp.Test { class TryCatchTest { static void Main(string[] args) { // Wir nehmen den Namen des Benutzers an Console.Write( "Name: " ); string name = Console.ReadLine(); // Wir nehmen das Alter des Benutzers an // Da die Eingabe als "String" erfolgt, // müssen wir sie casten. // Es reicht aus, einen Cast auf einen Short // zu machen, weil nur wenige Menschen älter // als 32.767 Jahre werden. short alter = 0; do { Console.Write( "Alter: " ); string bufAlter = Console.ReadLine(); try // ... der kritische Abschnitt { alter = System.Convert.ToInt16( bufAlter ); } catch // ... Was machen wir, wenn es schief geht? { Console.WriteLine( "Bitte geben Sie nur Zahlen ein!" ); } } while ( alter == 0 ); // Wir nehmen die Email-Adresse des Benutzers an. bool emailIstOkay = false; string email = ""; do { Console.Write( "Email: " ); email = Console.ReadLine(); try { // So sieht ein einfaches Muster aus // mit dem man Emails überprüfen kann. // Es findet nicht alle möglichen Varianten, // kann aber die meisten gebräuchlichen erkennen. string suchMuster = @"\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*"; // Wir prüfen die Email-Adresse mit Hilfe der RegEx-Klasse. // Dazu fragen wir die RegEx-Klasse, // ob die Email-Adresse das Suchmuster "matcht", // also ob es passt. // Wenn ja, haben wir eine gültige Email-Adresse. emailIstOkay = Regex.Match( email, suchMuster ).Success; } catch { Console.WriteLine ( "Die angegebene Email-Adresse ist ungültig!" ); } } while ( !emailIstOkay ); // Wir geben die Daten auf dem Bildschirm aus. Console.WriteLine( "Hallo {0}, du bist {1} Jahre alt, und deine Email-Adresse lautet {2}.", name, alter, email ); } } }