Arbeiten mit .NET: C-Sharp/ Ergänzungen/ Mehr zu Exception
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.
Wer den Abschnitt Ausnahmen und Fehler gelesen hat, wird sich vielleicht gewundert haben, ob das schon alles war. Und die Antwort lautet schlicht: "Alles Wesentliche; denn was wir uns jetzt noch anschauen, leistet zusätzliche Dienste."
Aber natürlich leistet es sehr gute zusätzliche Dienste, und deshalb lassen wir es auch nicht aus.
Try-Catch-Catch
[Bearbeiten]Bekanntlich gibt es eine Unzahl verschiedener möglicher Fehler. Wir möchten also gern in der Lage sein, jeden Fehler spezifisch zu behandeln, denn schließlich müssen wir anders reagieren, wenn wir einen Fehler "IOException: Datei nicht gefunden!" erhalten, als wenn wir einen Fehler "FormatException: Die Eingabezeichenfolge hat das falsche Format." bekommen. Und auch das können wir ganz einfach machen: Wir setzen einfach mehrere catch-Blöcke hintereinander ein:
private void DateiLaden()
{
System.IO.FileStream file = null;
System.IO.FileInfo fInfo = null;
try
{
// Wir öffnen die Datei "AccessLog.txt"
fInfo = new System.IO.FileInfo( "C:\\AccessLog.txt" );
file = fInfo.OpenRead();
// Hier lesen wir aus der Datei...
}
catch ( UnauthorizedAccessException fehlerNichtAutorisiert )
{
Console.WriteLine( "Der Zugriff ist nicht autorisiert." );
Console.WriteLine( "Folgender Fehler trat auf: {0}", fehlerNichtAutorisiert.Message );
}
catch ( DirectoryNotFoundException fehlerVerzeichnisNichtGefunden )
{
Console.WriteLine( "Das angegebene Verzeichnis wurde nicht gefunden." );
Console.WriteLine( "Folgender Fehler trat auf: {0}", fehlerVerzeichnisNichtGefunden.Message );
}
catch ( IOException fehlerIO )
{
Console.WriteLine( "Die Datei ist bereits geöffnet." );
Console.WriteLine( "Folgender Fehler trat auf: {0}", fehlerIO.Message );
}
catch ( Exception fehlerAllgemein )
{
Console.WriteLine( "Ein unbekannter Fehler ist aufgetreten." );
Console.WriteLine( "Folgender Fehler trat auf: {0}", fehlerAllgemein.Message );
throw fehlerAllgemein; // Exception "weiterwerfen, da wir sie nicht behandeln" können!
}
}
Jetzt haben wir jeden möglichen Fehler abgefangen. Wird uns der Zugriff verweigert, wird der Fehler UnauthorizedAccessException erzeugt und in den entsprechenden catch-Block gesprungen. Kann die Datei nicht gefunden werden, wird der Fehler DirectoryNotFoundException erzeugt und in dessen catch-Block gesprungen. Ist hingegen die Datei schon offen, wird der Fehler IOException erzeugt und in dessen Behandlungsabschnitt gesprungen. Und schließlich haben wir noch einen catch-Block für alle anderen Fehler geschrieben.
Grundsätzlich sollte man sich beim Ausnahmen fangen von den spezifischsten zu den allgemeineren durcharbeiten und nur solche Exceptions fangen, mit denen man auch umgehen kann. Der letzte catch-Block fängt zwar auch alle Excpetions, mit denen wir nichts anfangen können. Dies tut er, um den Benutzer darüber zu informieren, dass etwas schief gelaufen ist. Da wir diese Ausnahme aber nicht kennen, können wir auch nicht davon ausgehen, dass wir (wie im try-Block versucht) noch in der Lage sind, die Datei ordentlich zu schliessen. Deshalb werfen wir die Exception einfach weiter, so dass sich eine höhere Instanz darum kümmert (in der Regel wird dies das Betriebssystem sein, welches unsere Applikation dann halbwegs vernünftig zu beenden versucht).
Uns fehlt nur noch eines zu unserem Glück: Wenn wir die Datei nicht öffnen können, brauchen wir auch keine FileInfo davon erzeugen. Und wenn die Datei zwar geöffnet werden konnte, wie schließen wir sie dann wieder richtig? Zu diesem Zweck gibt es eine Erweiterung des try-catch.
Try-Catch-Finally
[Bearbeiten]Mit der Kombination von try-catch-finally können wir einen Block definieren, der in jedem Fall ausgeführt wird; egal, ob wir gut durchgekommen sind, oder ob ein Fehler auftrat, den wir entsprechend behandeln konnten.
try
{
// Versuche eine kritische Operation...
}
catch
{
// Der Patient ist tot...
// Spuren beseitigen, Schuldigen suchen, Alibi verschaffen.
}
finally
{
// Operationssaal aufräumen,
// egal ob der Patient tot ist oder nicht.
}
In unserem Beispiel von oben sieht das also nun so aus:
private void DateiLaden()
{
System.IO.FileStream file = null;
System.IO.FileInfo fInfo = null;
try
{
// Wir öffnen die Datei "AccessLog.txt"
fInfo = new System.IO.FileInfo( "C:\\AccessLog.txt" );
file = fInfo.OpenRead();
// Hier lesen wir aus der Datei...
}
catch ( UnauthorizedAccessException fehlerNichtAutorisiert )
{
Console.WriteLine( "Der Zugriff ist nicht autorisiert." );
Console.WriteLine( "Folgender Fehler trat auf: {0}", fehlerNichtAutorisiert.Message );
}
catch ( DirectoryNotFoundException fehlerVerzeichnisNichtGefunden )
{
Console.WriteLine( "Das angegebene Verzeichnis wurde nicht gefunden." );
Console.WriteLine( "Folgender Fehler trat auf: {0}", fehlerVerzeichnisNichtGefunden.Message );
}
catch ( IOException fehlerIO )
{
Console.WriteLine( "Die Datei ist bereits geöffnet." );
Console.WriteLine( "Folgender Fehler trat auf: {0}", fehlerIO.Message );
}
catch ( Exception fehler )
{
Console.WriteLine( "Ein unbekannter Fehler ist aufgetreten." );
Console.WriteLine( "Folgender Fehler trat auf: {0}", fehler.Message );
throw fehler;
}
finally
{
Console.WriteLine( "Datei wird geschlossen." );
if (file != null)
{
file.Close();
}
}
}
Der unspezifische letzte catch-Block wirft die Exception weiter und verhindert so, dass wir versuchen eine Datei zu schliessen und das aus irgendwelchen Gründen nicht können (z.B. weil der Benutzer das Kabel aus dem USB-Laufwerk gezogen hat auf welchem sich die Datei befindet). Zwar würde dieser Zustand im finally-Block dazu führen, dass einfach eine neue Exception erzeugt wird, das weiter werfen der Exception stellt die Vorgehensweise aber klarer dar.
Die Klasse Exception
[Bearbeiten]Gerade hatten wir die Klasse Exception als "General Failure" identifiziert, die jeden beliebigen Fehler auffängt, wenn wir das nicht selbst machen. Und aus dem Kapitel Vererbung ganz einfach wissen wir noch, dass solche "Allgemeinheit" darauf hinweist, dass wir es höchstwahrscheinlich mit einer Basisklasse zu tun haben. Tatsächlich ist die Klasse Exception sozusagen die Mutter aller Fehler im .NET. Von ihr stammen alle anderen Fehler ab.
Siehe auch: MSDN C# Referenz: Exception Klasse
Die Erben der Klasse Exception
[Bearbeiten]Und wie es sich gehört, erben natürlich alle Fehler von der Klasse Exception. Dabei hat das .NET-Framework bereits eine Vielzahl möglicher Fehler eingebaut, die einzeln aufzuzählen hier praktisch den Rahmen sprengen würde. Wir werden in diesem Buch dem einen oder anderen Fehler begegnen. Es reicht aus, dann zu wissen, dass all diese Fehler von der Klasse Exception erben.
Siehe auch die wichtigsten rund 100 Fehler-Klassen im MSDN:
MSDN .NET Referenz: Exception Hierarchie
MSDN .NET Referenz: SystemException Hierarchie
MSDN .NET Referenz: ApplicationException Hierarchie
Eigene Fehler-Klassen
[Bearbeiten]Es gibt aber Situationen, da reichen uns die eingebauten Fehler nicht aus. Beispielsweise möchten wir bereits im Fehler selbst festhalten, wer ihn ausgelöst hat. Wenn wir uns jetzt auf die Suche machen, welcher Fehler diese Information speichern kann, werden wir vergeblich suchen. Es bleibt uns nichts anderes übrig, als diese Idee aufzugeben --- oder eine eigene Fehlerklasse zu schreiben. Im Abschnitt Vererbung ganz einfach haben wir schließlich gelernt, wie man das macht.
Da es zum wirklich schlechten Stil gehört, einfach die Mutter aller Fehler, also die Exception-Klasse, zu bemühen, suchen wir uns den Fehler aus, der unseren Wünschen am nächsten kommt. Da wir ja festhalten wollten, welcher Benutzer den Fehler ausgelöst hat, nehmen wir für unser Beispiel die UnauthorizedAccessException-Klasse, von der wir erben wollen.
class ExceptionUserAccess : UnauthorizedAccessException
{
// Wir fügen ein neues Feld hinzu.
private string m_UserName;
// Wir fügen eine neue Eigenschaft hinzu.
public string UserName
{
get { return m_UserName; }
set { m_UserName = value; }
}
}
... und schon können wir die Klasse einsetzen. (Um das Beispiel nicht unnötig aufzublähen, beschränken wir uns auf den Autorisierungsfehler:
private void DateiLaden()
{
System.IO.FileStream file = null;
System.IO.FileInfo fInfo = null;
try
{
try
{
// Wir öffnen die Datei "AccessLog.txt"
fInfo = new System.IO.FileInfo( "C:\\AccessLog.txt" );
file = fInfo.OpenRead();
// Hier lesen wir aus der Datei...
}
catch ( UnauthorizedAccessException fehlerNichtAutorisiert )
{
Console.WriteLine( "Der Zugriff ist nicht autorisiert." );
Console.WriteLine( "Folgender Fehler trat auf: {0}", fehlerNichtAutorisiert.Message );
// Wir holen uns ein Objekt der eigenen Fehlerklasse
ExceptionUserAccess eigenerFehler = new ExceptionUserAccess();
// Wir tragen ein, welcher Benutzer betroffen ist.
eigenerFehler.UserName = System.Threading.Thread.CurrentPrincipal.Identity.Name;
// Wir werfen den selbstgebauten Fehler einfach in die Runde...
throw eigenerFehler;
}
}
catch ( ExceptionUserAccess fehlerBenutzerzugriff )
{
// Wir behandeln unseren eigenen Fehler.
Console.WriteLine( fehlerBenutzerzugriff.UserName );
}
}
Wie man sehen kann, lassen sich try-catch-Blöcke auch schachteln. Gleichzeitig haben wir so ganz nebenbei eine weitere Technik der Ausnahme- und Fehlerbehandlung ausprobiert: Das Bubbling von Fehlern.