{"id":55000037,"date":"2016-06-01T00:00:00","date_gmt":"2024-04-25T16:15:09","guid":{"rendered":"http:\/\/access-im-unternehmen.aix-dev.de\/aiu\/?p=37"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-30T00:00:00","slug":"Von_VBA_zu_C_Fehlerbehandlung","status":"publish","type":"post","link":"https:\/\/vbentwickler.de\/Von_VBA_zu_C_Fehlerbehandlung\/","title":{"rendered":"Von VBA zu C#: Fehlerbehandlung"},"content":{"rendered":"<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/vg05.met.vgwort.de\/na\/43e926d0b4f3454ca845fad1949d54a5\" width=\"1\" height=\"1\" alt=\"\"><\/p>\n<p><b>Nur wenige Access-Programmierer statten ihre Anwendungen mit einer ordentlichen Fehlerbehandlung aus. F&uuml;r viele ist dies ein leidiges Thema. Die Fehlerbehandlung sorgt im Optimalfall sowohl unter VBA als auch unter C# daf&uuml;r, dass eine Anwendung stabiler wird und nach dem Auftreten von Laufzeitfehlern nicht unerwartet reagiert oder sogar abst&uuml;rzt. W&auml;hrend es unter VBA nur wenige Konstrukte gibt, um eine Fehlerbehandlung zu implementieren, bietet C# schon eine Menge mehr. Dieser Artikel liefert eine Einf&uuml;hrung und zeigt, wie Sie die von VBA gewohnten Techniken unter C# einsetzen.<\/b><\/p>\n<p>Die minimale Fehlerbehandlung unter VBA sieht so aus, dass Sie vor fehlertr&auml;chtigen Anweisungen die eingebaute Fehlerbehandlung mit der folgenden Programmzeile deaktivieren:<\/p>\n<pre>On Error Resume <span style=\"color:blue;\">Next<\/span><\/pre>\n<p>Alle folgenden Fehler in der aktuellen Routine und in solchen, die von dieser Routine aufgerufen werden, werden nicht behandelt. Dadurch gibt es zwar immerhin keine Fehlermeldung und es werden keine Variablen geleert, was beim Auftreten unbehandelter Fehler auftreten kann. Allerdings erledigen fehlerhafte Zeilen ihre Aufgabe nicht, was zu Folgefehlern (auch logischen Fehlern) in den folgenden Anweisungen f&uuml;hren kann.<\/p>\n<p>Sp&auml;testens mit dem Ende der Routine endet die Deaktivierung der eingebauten Fehlerbehandlung. Vorher erhalten Sie dies mit der folgenden Anweisung:<\/p>\n<pre>On Error Goto 0<\/pre>\n<p>Dadurch reagiert VBA wieder wie gewohnt mit entsprechenden Fehlermeldungen auf Fehler.<\/p>\n<p>Dazwischen haben Sie Gelegenheit, benutzerdefiniert auf die auftretenden Fehler zu reagieren &#8211; beispielsweise, indem Sie den Wert der Eigenschaft <b>Number <\/b>des <b>Err<\/b>-Objekts auslesen und f&uuml;r die interessanten Werte entsprechende Fehlerbehandlungen hinzuf&uuml;gen.<\/p>\n<p>Dies kann beispielsweise wie folgt aussehen:<\/p>\n<pre>On Error Resume <span style=\"color:blue;\">Next<\/span>\r\n<span style=\"color:blue;\">Debug.Print<\/span> 1\/0\r\nSelect Case Err.Number\r\n     <span style=\"color:blue;\">Case <\/span>11\r\n         <span style=\"color:blue;\">MsgBox<\/span> \"Fehler: Teilen durch 0.\"\r\n<span style=\"color:blue;\">End Select<\/span>\r\nOn Error Goto 0<\/pre>\n<p>Unter C# gibt es zur Fehlerbehandlung andere M&ouml;glichkeiten, die Sie in den folgenden Abschnitten kennen lernen.<\/p>\n<h2>Warum &uuml;berhaupt eine Fehlerbehandlung?<\/h2>\n<p>Um auf Nummer sicher zu gehen, wollen wir zuvor noch einmal kurz auf die Gr&uuml;nde f&uuml;r die Programmierung einer benutzerdefinierten Fehlerbehandlung eingehen. Wenn Sie Code programmieren, der fehlerhafte Eingaben zul&auml;sst und keine entsprechende benutzerdefinierte Fehlerbehandlung aufweist, erhalten Sie beispielsweise bei einer Konsolenanwendung eine Meldung plus Fehlerbehandlungsfenster wie in Bild 1.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2016_03\/pic_37_002.png\" alt=\"Unbehandelte Ausnahme bei einer Konsolenanwendung\" width=\"500\" height=\"278,4237\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 1: Unbehandelte Ausnahme bei einer Konsolenanwendung<\/span><\/b><\/p>\n<p>Wenn Sie einen solchen Fehler in einer WPF-Anwendung ausl&ouml;sen, erhalten Sie noch nicht einmal einen kleinen Hinweis auf den Fehler im Code, der zu dieser Ausnahme f&uuml;hrte (siehe Bild 2).<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2016_03\/pic_37_003.png\" alt=\"Unbehandelte Ausnahme bei einer Windows-Anwendung\" width=\"500\" height=\"337,9121\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 2: Unbehandelte Ausnahme bei einer Windows-Anwendung<\/span><\/b><\/p>\n<p>Noch &uuml;bler wird es, wenn nicht Sie selbst die Software bedienen, sondern ein Kunde: Erstens macht dies nie einen guten Eindruck, zweitens kann der Kunde auf Basis der hier gelieferten Fehlerinformationen kaum helfen, den Fehler zu finden.<\/p>\n<p>Also sollten Sie daf&uuml;r sorgen, dass der Kunde einen aussagekr&auml;ftigen Text als Fehlermeldung erh&auml;lt, der ihn beispielsweise bei einem Eingabefehler auf die Ursache des Fehlers hinweist oder aber ihm die Informationen liefert, die er zur Behebung des Problems an den Entwickler der Software weitergeben kann.<\/p>\n<p>Der wichtigste Punkt beim Implementieren einer benutzerdefinierten Fehlerbehandlung etwa unter VBA ist jedoch die stabile Fortsetzung der Anwendung: Wenn ein Fehler aufgetreten ist, beispielsweise durch eine Fehleingabe oder einen fehlerhaften Zugriff auf ein Element, dann sollte die Anwendung nach der Ausgabe der Fehlermeldung fortgesetzt werden k&ouml;nnen und nicht einfach abbrechen. Dabei ist au&szlig;erdem sicherzustellen, dass die Funktion und der stabile Zustand der Anwendung durch den Fehler nicht beeintr&auml;chtigt werden. Bei VBA war das mitunter ganz einfach deshalb nicht der Fall, weil durch unbehandelte Fehler Variableninhalte gel&ouml;scht oder Objekte zerst&ouml;rt wurden. Wurden die Zeilen, die den Fehler ausl&ouml;sten, hingegen per <b>On Error Resume Next <\/b>einer benutzerdefinierten Fehlerbehandlung zugef&uuml;hrt, statt einfach unbehandelte Fehler auszul&ouml;sen, war zumindest schon einmal der Inhalt und der Zustand der Variablen sichergestellt.<\/p>\n<h2>Fehlerhafter Code<\/h2>\n<p>Schauen wir uns das Beispiel an, das zum Ausl&ouml;sen der Ausnahme der Konsolenanwendung f&uuml;hrte. Den Code finden Sie in Listing 1. Die Zeile, die das Ergebnis der Division der Werte der Variablen <b>Dividend <\/b>und <b>Divisor <\/b>ermitteln soll, l&ouml;st die Ausnahme aus, wenn der Divisor zuvor den Wert <b>0 <\/b>zugewiesen bekommen hat &#8211; der Fehler tritt also durch eine Division durch <b>0 <\/b>auf.<\/p>\n<pre>static void Main(string[] args) {\r\n     Console.WriteLine(\"Geben Sie den Dividend und den Divisor als ganze Zahlen ein.\");\r\n     Console.WriteLine(\"Dividend:\");\r\n     decimal Dividend = Convert.ToDecimal(Console.ReadLine());\r\n     Console.WriteLine(\"Divisor:\");\r\n     decimal Divisor = Convert.ToDecimal (Console.ReadLine());\r\n     decimal Quotient = Dividend \/ Divisor;\r\n     Console.WriteLine(\"Der Quotient lautet: {0}\", Quotient);\r\n     Console.ReadLine();\r\n}<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 1: Methode, die einen Fehler ausl&ouml;st, wenn als Divisor der Wert 0 angegeben wird<\/span><\/b><\/p>\n<p>Wenn Sie die Anwendung debuggen, also diese von Visual Studio aus etwa mit der Taste <b>F5 <\/b>starten, erhalten Sie einige Fehlerinformationen mehr. Au&szlig;erdem markiert Visual Studio gleich die fehlerhafte Zeile (siehe Bild 3). Es gibt sogar noch Tipps zur Problembehandlung.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2016_03\/pic_37_001.png\" alt=\"Ausl&ouml;sen einer Ausnahme unter C#\" width=\"600\" height=\"524,021\"\/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 3: Ausl&ouml;sen einer Ausnahme unter C#<\/span><\/b><\/p>\n<p>Wenn Sie m&ouml;chten, erhalten Sie auch noch weitere Details, und zwar durch einen Mausklick auf den Link <b>Details anzeigen &#8230;<\/b>, der den Dialog aus Bild 4 &ouml;ffnet.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2016_03\/pic_37_004.png\" alt=\"Details zu einer Ausnahme beim Debuggen in Visual Studio\" width=\"600\" height=\"234,873\"\/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 4: Details zu einer Ausnahme beim Debuggen in Visual Studio<\/span><\/b><\/p>\n<p>F&uuml;r Fehler wie diese gibt es einige Beispiele &#8211; die Eingabe unzul&auml;ssiger Werte wie in diesem Beispiel, Zugriff auf nicht vorhandene Dateien et cetera.<\/p>\n<h2>try&#8230;catch statt On Error Resume Next<\/h2>\n<p>Unter C# k&ouml;nnen Sie Laufzeitfehler in sogenannten <b>try&#8230;catch<\/b>-Bl&ouml;cken behandeln. Das sieht dann so aus, dass Sie den Code, der einen Fehler ausl&ouml;sen k&ouml;nnte, in den <b>try<\/b>-Block packen und den Code, der ausgel&ouml;st werden soll, wenn innerhalb des <b>try<\/b>-Blocks ein Fehler auftritt, in den <b>catch<\/b>-Block.<\/p>\n<p>In unserem Fall wollen wir die Zeile, welche die Division durchf&uuml;hrt, in den <b>try<\/b>-Block &uuml;berf&uuml;hren. Gleichzeitig f&uuml;gen wir dort auch die Zeile ein, welche das Ergebnis aus der Variablen <b>Quotient <\/b>in der Konsole ausgibt &#8211; sonst ergibt dies einen Syntaxfehler, weil <b>Quotient <\/b>nicht in jedem Falle deklariert und initialisiert wird.<\/p>\n<p>In den <b>catch<\/b>-Block schreiben wir eine Anweisung, welche einen Hinweis auf das Auftreten eines Fehlers in der Konsole ausgibt (siehe Listing 2). Wie Sie hier erkennen, wird die folgende Anweisung, die ja das Ergebnis der Berechnung ausgeben sollte, ignoriert &#8211; dies gilt grunds&auml;tzlich f&uuml;r alle Anweisungen, die der fehlerhaften Anweisung folgen.<\/p>\n<pre>try {\r\n     decimal Quotient = Dividend \/ Divisor;\r\n     Console.WriteLine(\"Der Quotient lautet: {0}\", Quotient);\r\n}\r\ncatch {\r\n     Console.WriteLine(\"Ups! Es ist ein Fehler beim Rechnen aufgetreten!\");\r\n}<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 2: Abfangen eines Fehlers per try&#8230;catch-Block<\/span><\/b><\/p>\n<p>Nun erh&auml;lt der Benutzer zwar auch keine wesentlich aussagekr&auml;ftigere Meldung, aber daf&uuml;r wird das Programm auch nicht einfach abgebrochen. Au&szlig;erdem haben wir ja auch noch gar nicht gepr&uuml;ft, um was f&uuml;r einen Fehler es sich handelt &#8211; dies pr&uuml;fen wir in der folgenden Version.<\/p>\n<p>Die Anwendung wird dann mit den Anweisungen fortgesetzt, die nach dem Ende des <b>catch<\/b>-Blocks folgen. Dies erkennen Sie in diesem Beispiel daran, dass das Bet&auml;tigen der Eingabetaste das Programm beendet, weil die letzte <b>Console.ReadLine()<\/b>-Methode ausgef&uuml;hrt wird (siehe Bild 5).<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2016_03\/pic_37_005.png\" alt=\"Benutzerdefinierte Fehlermeldung beim Auftreten der Ausnahme\" width=\"500\" height=\"179,4872\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 5: Benutzerdefinierte Fehlermeldung beim Auftreten der Ausnahme<\/span><\/b><\/p>\n<h2>Exception statt Err<\/h2>\n<p>Unter C# hei&szlig;t das Objekt, das die per Code auswertbaren Fehlerinformationen enth&auml;lt, <b>Exception<\/b>. Es entspricht etwa dem Objekt <b>Err <\/b>unter VBA, das ja mit seinen Eigenschaften <b>Description <\/b>oder <b>Number <\/b>oft hilfreiche Informationen liefert.<\/p>\n<p>Damit Sie dieses Objekt nutzen k&ouml;nnen, m&uuml;ssen Sie es f&uuml;r den <b>catch<\/b>-Zweig als Parameter hinzuf&uuml;gen, also wie in Listing 3 mit <b>catch (Exception e)<\/b>.<\/p>\n<pre>catch (Exception e) {\r\n     Console.WriteLine(\"Meldung des Exception-Objekts:\\n{0}\\n\", e.Message);\r\n     Console.WriteLine(\"Umfangreiche Informationen:\\n{0}\\n\", e.GetBaseException().ToString());\r\n     Console.WriteLine(\"Typ der Ausnahme:\\n{0}\", e.GetType().ToString());\r\n}<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 3: Ausstatten des catch-Block mit einigen weiteren Informationen<\/span><\/b><\/p>\n<p>Die folgenden drei Zeilen des Beispiels geben verschiedene Informationen auf die Konsole aus, die Sie auch in Bild 6 sehen. Die erste ist der Inhalt der Eigenschaft <b>Message<\/b>. Die Eigenschaft <b>GetBaseException <\/b>liefert den Text, den auch ein unbehandelter Fehler auf die Konsole zaubert. Schlie&szlig;lich gibt uns die Eigenschaft <b>GetType <\/b>hilfreiche Informationen, wenn es darum geht, den Typ der Ausnahme zu ermitteln. In diesem Fall lautet der Typ <b>DivideByZeroException<\/b>. Damit k&ouml;nnen wir sp&auml;ter gezielt auf bestimmte Fehler reagieren.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2016_03\/pic_37_006.png\" alt=\"Erweiterte Fehlermeldung des Exception-Objekts\" width=\"600\" height=\"334,8609\"\/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 6: Erweiterte Fehlermeldung des Exception-Objekts<\/span><\/b><\/p>\n<h2>Allgemeine Exception<\/h2>\n<p>Was wir im vorherigen Beispiel getan haben, war die Behandlung einer allgemeinen Exception. Die Klasse <b>Exception <\/b>ist quasi die Mutter aller Fehlerklassen. Es gibt eine ganze Reihe von Fehlerklassen, die von dieser Klasse abgeleitet sind.<\/p>\n<p>Wenn Sie im <b>catch<\/b>-Zweig einer Ausnahmebehandlung in Klammern ein Objekt des Typs <b>Exception <\/b>&uuml;bergeben, fangen Sie damit alle m&ouml;glichen Ausnahmen ab. Dies entspricht etwa dem <b>Case Else<\/b>-Zweig in einer VBA-Fehlerbehandlung wie der folgenden:<\/p>\n<pre>Select Case Err.Number\r\n     <span style=\"color:blue;\">Case <\/span>11 ''Geteilt durch Null\r\n         ''Fehler behandeln\r\n     <span style=\"color:blue;\">Case <\/span>0 ''kein Fehler, nichts ist zu tun\r\n     <span style=\"color:blue;\">Case Else<\/span>\r\n         ''Alle &uuml;brigen Fehlernummern behandeln\r\n<span style=\"color:blue;\">End Select<\/span><\/pre>\n<p>Nun wollen wir noch wissen, wie wir einen speziellen Fehler abfangen, in diesem Fall die <b>DivideByZeroException<\/b>. Wenn Sie nur diesen einen Fehler abfangen m&ouml;chten, reicht die folgende Variante aus:<\/p>\n<pre>try {\r\n     decimal Quotient = Dividend \/ Divisor;\r\n     Console.WriteLine(\"Der Quotient lautet: {0}\", Quotient);\r\n}\r\ncatch (DivideByZeroException e) {\r\n     Console.WriteLine(\"Der Dividend darf nicht 0 sein.\");\r\n}<\/pre>\n<p>Der <b>catch<\/b>-Zweig wird in diesem Fall nur angesteuert, wenn eine <b>DivideByZero<\/b>-Ausnahme ausgel&ouml;st wird. Im Falle eines jeden anderen Fehlers tritt eine unbehandelte Ausnahme auf.<\/p>\n<p>Um einen weiteren, andersartigen Fehler auszul&ouml;sen und behandeln zu k&ouml;nnen, ziehen wir die &uuml;brigen Anweisungen der Methode wie in Listing 4 ebenfalls in den <b>try<\/b>-Block hinein. Au&szlig;erdem f&uuml;gen wir neben dem <b>catch<\/b>-Block, der sich um den Fehler k&uuml;mmert, der beim Teilen durch <b>0 <\/b>auftritt, einen weiteren <b>catch<\/b>-Block hinzu. Dieser soll wieder alle &uuml;brigen Fehler abfangen.<\/p>\n<pre>public static void Andere_Exception() {\r\n     try {\r\n         Console.WriteLine(\"Geben Sie den Dividend und den Divisor als ganze Zahlen ein.\");\r\n         Console.WriteLine(\"Dividend:\");\r\n         decimal Dividend = Convert.ToDecimal(Console.ReadLine());\r\n         Console.WriteLine(\"Divisor:\");\r\n         decimal Divisor = Convert.ToDecimal(Console.ReadLine());\r\n         decimal Quotient = Dividend \/ Divisor;\r\n         Console.WriteLine(\"Der Quotient lautet: {0}\", Quotient);\r\n     }\r\n     catch (DivideByZeroException e) {\r\n         Console.WriteLine(\"Der Dividend darf nicht 0 sein.\");\r\n     }\r\n     catch (Exception e) {\r\n         Console.WriteLine(\"Es ist ein Fehler aufgetreten: {0}\", e.GetType().ToString());\r\n     }\r\n     Console.ReadLine();\r\n}<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 4: Abfangen eines speziellen und eines allgemeinen Fehlers per try&#8230;catch-Block<\/span><\/b><\/p>\n<p>Wenn wir nun beispielsweise f&uuml;r die erste <b>Console.ReadLine<\/b>-Anweisung keine Zahl, sondern eine Zeichenkette eingeben, erhalten wir einen weiteren Fehler. Dieser wird ausgel&ouml;st, da wir das Ergebnis der Eingabe direkt in einen Wert des Datentyps <b>decimal <\/b>umwandeln wollen. Das gelingt mit einer Zeichenkette nat&uuml;rlich nicht, also liefert dies eine weitere Ausnahme &#8211; wie Bild 7 zeigt.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2016_03\/pic_37_007.png\" alt=\"Fehler durch die Eingabe von Daten im falschen Format\" width=\"500\" height=\"212,963\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 7: Fehler durch die Eingabe von Daten im falschen Format<\/span><\/b><\/p>\n<p>Nachdem wir wissen, dass dies die Ausnahme <b>FormatException <\/b>ausl&ouml;st, f&uuml;gen wir auch diese als weiteren <b>catch<\/b>-Block zur Fehlerbehandlung hinzu:<\/p>\n<pre>try {\r\n     ...\r\n}\r\ncatch (DivideByZeroException e) {\r\n     Console.WriteLine(\"Der Dividend darf nicht 0 sein.\");\r\n}\r\ncatch (FormatException e) {\r\n     Console.WriteLine(\"Bitte geben Sie eine ganze Zahl ein.\");\r\n}\r\ncatch (Exception e) {\r\n     Console.WriteLine(\"Es ist ein Fehler aufgetreten: \r\n         {0}\", e.GetType().ToString());\r\n}<\/pre>\n<p>Auf &auml;hnliche Weise legen wir f&uuml;r alle Fehler, die auftreten k&ouml;nnen, eine Ausnahmebehandlung an (zumindest f&uuml;r die, die wir bereits erkennen k&ouml;nnen &#8211; der Benutzer wird sicher noch weitere Schwachstellen aufdecken &#8230;).<\/p>\n<p>Die Reihenfolge der <b>catch<\/b>-Bl&ouml;cke spielt nat&uuml;rlich eine &uuml;bergeordnete Rolle, gerade wenn Sie eine allgemeine Ausnahmebehandlung (mit <b>catch (Exception e)<\/b>) und speziellere Ausnahmebehandlungen (wie mit <b>catch (DivideByZeroException)<\/b>) implementieren. Die catch-Bl&ouml;cke werden n&auml;mlich immer in der angegebenen Reihenfolge abgearbeitet. Wenn Sie also direkt in der ersten <b>catch<\/b>-Anweisung auf die allgemeine Ausnahme <b>Exception <\/b>pr&uuml;fen, wird diese bei jeder denkbaren Ausnahme angesteuert, da ja alle anderen Ausnahmen von <b>Exception <\/b>erben. Aber Visual Studio beugt dem direkt vor, denn untergeordnete Ausnahmen m&uuml;ssen immer vor &uuml;bergeordneten Ausnahmen abgearbeitet werden. Wenn wir versuchen, die allgemeine <b>Exception<\/b>-Ausnahme vor der <b>DivideByZeroException<\/b>-Ausnahme zu behandeln, erhalten wir einen Kompilierfehler (siehe Bild 8).<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2016_03\/pic_37_008.png\" alt=\"Exceptions m&uuml;ssen in der Hierarchie von unten nach oben abgearbeitet werden.\" width=\"649,4275\" height=\"467,8828\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 8: Exceptions m&uuml;ssen in der Hierarchie von unten nach oben abgearbeitet werden.<\/span><\/b><\/p>\n<h2>Hierarchie der Exceptions<\/h2>\n<p>Damit kommen wir zur&uuml;ck zur Mutter aller Fehlerklassen und den untergeordneten Elementen. Woher erfahren wir, in welcher Hierarchie-Ebene sich eine <b>&#8230;Exception<\/b>-Klasse befindet und welche die &uuml;bergeordneten Klassen sind?<\/p>\n<p>Dazu klicken Sie etwa im Code-Fenster mit der rechten Maustaste auf den Text <b>DivideByZeroException <\/b>und w&auml;hlen dort den Eintrag <b>Definition einsehen <\/b>aus. Dies &ouml;ffnet einen gelb hinterlegten Bereich, der die &ouml;ffentliche Klasse beschreibt, die von der Klasse <b>ArithmeticException <\/b>abgeleitet ist (siehe Bild 9). Diese Abh&auml;ngigkeit erkennen Sie an dem Doppelpunkt zwischen den beiden Klassennamen (<b>DivideByZeroException : ArithmeticException<\/b>).<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2016_03\/pic_37_009.png\" alt=\"Die Definition einer Klasse liefert Hinweise auf die &uuml;bergeordnete Klasse\" width=\"649,4275\" height=\"293,7182\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 9: Die Definition einer Klasse liefert Hinweise auf die &uuml;bergeordnete Klasse<\/span><\/b><\/p>\n<p>Die Klasse <b>ArithmeticException <\/b>wird also vermutlich s&auml;mtliche Ausnahmen behandeln, die durch die Verwendung von Rechenoperatoren unter C# ausgel&ouml;st werden, wobei <b>DivideByZeroException <\/b>nur ein Spezialfall ist.<\/p>\n<p>Es geht aber noch weiter: Wenn Sie in der Definition mit der rechten Maustaste auf den Eintrag <b>ArithmeticException <\/b>klicken und dann wiederum den Kontextmen&uuml;-Eintrag <b>Definition einsehen <\/b>ausw&auml;hlen, erhalten Sie die Definition der Klasse <b>ArithmeticException <\/b>und mit <b>SystemException <\/b>die &uuml;bergeordnete Ausnahmeklasse. Dass die Hierarchie einigerma&szlig;en flach ist, erkennen Sie, wenn Sie auch noch die &uuml;bergeordnete Klasse von <b>SystemException <\/b>ermitteln &#8211; hier landen Sie dann n&auml;mlich bei der Klasse <b>Exception<\/b>, die keine weitere &uuml;bergeordnete Ausnahmeklasse besitzt.<\/p>\n<h2>Exception mit oder ohne Variable?<\/h2>\n<p>In allen bisherigen Beispielen haben wir das <b>Exception<\/b>-Objekt in einer Variablen namens <b>e <\/b>gespeichert, um innerhalb des <b>catch<\/b>-Blocks auf die Eigenschaften des Ausnahme-Objekts zugreifen zu k&ouml;nnen. Diese Variable k&ouml;nnen Sie auch weglassen, wenn Sie gar nicht auf die Eigenschaften zugreifen wollen. Meist ist dies nicht notwendig &#8211; im Falle der <b>DivideByZeroException <\/b>etwa gibt es in der betroffenen Methode ja nur eine Zeile, die diesen Fehler ausl&ouml;sen kann, und der Grund daf&uuml;r ist dann auch offensichtlich. Sie k&ouml;nnen e also auch einfach weglassen:<\/p>\n<pre>...\r\ncatch (DivideByZeroException) {\r\n     Console.WriteLine(\"Der Dividend darf nicht 0 sein.\");\r\n}\r\n...<\/pre>\n<p>In diesem Fall erscheinen dann auch keine Warnungen wie <b>Die Variable &#8220;e&#8221; ist deklariert, wird aber nie verwendet.<\/b> mehr in der Fehlerliste von Visual Studio.<\/p>\n<h2>Ausnahme in &uuml;bergeordneter Methode verarbeiten<\/h2>\n<p>Manchmal m&ouml;chten Sie Fehler direkt an Ort und Stelle verarbeiten, also beispielsweise in der Methode, in der die Ausnahme ausgel&ouml;st wurde. Es kann jedoch auch F&auml;lle geben, wo die Ausnahme in einer &uuml;bergeordneten Methode behandelt werden soll &#8211; also in der Methode, von der aus die untergeordnete Methode aufgerufen wurde.<\/p>\n<p>Ein Beispiel finden Sie in Listing 5. Hier ruft die Methode <b>FehlerbehandlungUebergeordnet <\/b>die Methode <b>FehlerbehandlungUntergeordnet <\/b>auf, packt aber diesen Aufruf in einen <b>try<\/b>-Block. Was geschieht nun? Obwohl die Methode <b>FehlerbehandlungUntergeordnet <\/b>keine eigene Behandlung von Ausnahmen enth&auml;lt, wird der Fehler aufgrund der Fehlerbehandlung in der &uuml;bergeordneten Fehlerbehandlung behandelt.<\/p>\n<pre>public static void FehlerbehandlungUebergeordnet() {\r\n     try {\r\n         FehlerbehandlungUntergeordnet();\r\n     }\r\n     catch (Exception e) {\r\n         Console.WriteLine(\"Es ist ein Fehler aufgetreten: {0}\", e.GetType().ToString());\r\n     }\r\n}\r\nprivate static void FehlerbehandlungUntergeordnet() {\r\n     decimal Null = 0;\r\n     decimal Quotient = 1 \/ Null;\r\n     Console.WriteLine(\"Der Quotient lautet: {0}\", Quotient);\r\n     Console.ReadLine();\r\n}<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 5: Fehler in untergeordneter Methode behandeln<\/span><\/b><\/p>\n<p>Nat&uuml;rlich k&ouml;nnen Sie auch noch eine individuelle Ausnahme-Behandlung zur untergeordneten Methode hinzuf&uuml;gen. Dies w&uuml;rde dann etwa wie in Listing 6 aussehen. Hier wird der Fehler, der durch die Division durch <b>0 <\/b>auftritt, direkt an Ort und Stelle behandelt. Alle anderen Fehler, also beispielsweise durch Eingeben eines Textes statt einer Zahl, werden in der allgemeinen Ausnahmebehandlung in der &uuml;bergeordneten Methode behandelt. Wenn Sie auf diese Weise mehrere Methoden von der Hauptmethode aus aufrufen, brauchen Sie den aufgerufenen Methoden nat&uuml;rlich keinen <b>catch (Exception)<\/b>-Zweig mehr zuzuweisen.<\/p>\n<pre>public static void FehlerbehandlungOben() {\r\n     try {\r\n         FehlerbehandlungUnten();\r\n     }\r\n     catch (Exception e) {\r\n         Console.WriteLine(\"Es ist ein Fehler aufgetreten: {0}\", e.GetType().ToString());\r\n     }\r\n}\r\nprivate static void FehlerbehandlungUnten() {\r\n     try {\r\n         Console.WriteLine(\"Geben Sie den Dividend und den Divisor als ganze \r\n             Zahlen ein.\");\r\n         Console.WriteLine(\"Dividend:\");\r\n         decimal Dividend = Convert.ToDecimal(Console.ReadLine());\r\n         Console.WriteLine(\"Divisor:\");\r\n         decimal Divisor = Convert.ToDecimal(Console.ReadLine());\r\n         decimal Quotient = Dividend \/ Divisor;\r\n         Console.WriteLine(\"Der Quotient lautet: {0}\", Quotient);\r\n     }\r\n     catch (DivideByZeroException e) {\r\n         Console.WriteLine(\"Der Dividend darf nicht 0 sein.\");\r\n     }\r\n     Console.ReadLine();\r\n}<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 6: Fehlerbehandlung in untergeordneter und &uuml;bergeordneter Methode<\/span><\/b><\/p>\n<p>Auf diese Weise k&ouml;nnen Sie &uuml;brigens die vorgeschriebene Reihenfolge bei der Abarbeitung der Ausnahmen in ihrer Hie-rarchie unterlaufen: Wenn Sie in der aufgerufenen Methode <b>catch (Exception) <\/b>unterbringen und in der aufrufenden Methode <b>catch (DivideByZeroException)<\/b>, beanstandet Visual Studio das nicht.<\/p>\n<h2>Aufr&auml;umarbeiten nach Fehlern: der finally-Block<\/h2>\n<p>Grunds&auml;tzlich sind wir mit dem <b>try<\/b>&#8211; und den <b>catch<\/b>-Bl&ouml;cken gut ausgestattet: Wir legen mit dem <b>try<\/b>-Block fest, f&uuml;r welchen Code-Abschnitt wir auf Fehler reagieren m&ouml;chten. Mit dem <b>catch<\/b>-Block k&ouml;nnen wir verschiedene spezifische oder auch allgemeine Ausnahmen behandeln.<\/p>\n<p>Was aber, wenn wir nach Auftreten eines Fehlers die Methode verlassen m&ouml;chten, in dieser Methode aber noch Aufr&auml;um-arbeiten durchzuf&uuml;hren sind? Unter VBA haben wir das beispielsweise wie folgt programmiert:<\/p>\n<pre><span style=\"color:blue;\">Public Sub <\/span>Fehlerbehandlung()\r\n     <span style=\"color:blue;\">On Error GoTo<\/span> Fehler\r\n     <span style=\"color:blue;\">Debug.Print<\/span> 1 \/ 0\r\nRestarbeiten:\r\n     ''Restarbeiten\r\n     ''...\r\n     <span style=\"color:blue;\">Exit Sub<\/span>\r\nFehler:\r\n     ''Fehlerbehandlung\r\n     ''...\r\n     GoTo Restarbeiten\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p>Trat hier kein Fehler auf, wurden die Anweisungen vor der Sprungmarke <b>Restarbeiten <\/b>durchgef&uuml;hrt, danach die hinter <b>Restarbeiten <\/b>aufgef&uuml;hrten Anweisungen. Damit die hinter der Sprungmarke <b>Fehler <\/b>aufgef&uuml;hrten Anweisungen f&uuml;r die Fehlerbehandlung nur im Fehlerfall aufgerufen werden, haben wir dort zuvor die Anweisung <b>Exit Sub <\/b>eingef&uuml;gt.<\/p>\n<p>Ist hingegen ein Fehler aufgetreten, erfolgte der &uuml;ber die <b>On Error Goto &#8230;<\/b>-Anweisung angegebene Sprung zur Sprungmarke <b>Fehler<\/b>. Nach Ausf&uuml;hren der dortigen Anweisungen sollte die Prozedur noch zu den Anweisungen unter Restarbeiten springen, bevor diese endet.<\/p>\n<p>Unter C# k&ouml;nnten wir die nach Auftreten des Fehlers auszuf&uuml;hrenden Anweisungen einfach hinter dem letzten <b>catch<\/b>-Block anh&auml;ngen (siehe Listing 7). Die dort befindlichen Anweisungen werden dann vor dem Verlassen der Methode noch ausgef&uuml;hrt.<\/p>\n<pre>public static void Restarbeiten() {\r\n     try {\r\n         Console.WriteLine(\"Geben Sie den Dividend und den Divisor als ganze Zahlen ein.\");\r\n         Console.WriteLine(\"Dividend:\");\r\n         decimal Dividend = Convert.ToDecimal(Console.ReadLine());\r\n         Console.WriteLine(\"Divisor:\");\r\n         decimal Divisor = Convert.ToDecimal(Console.ReadLine());\r\n         decimal Quotient = Dividend \/ Divisor;\r\n         Console.WriteLine(\"Der Quotient lautet: {0}\", Quotient);\r\n     }\r\n     catch (DivideByZeroException) {\r\n         Console.WriteLine(\"Der Dividend darf nicht 0 sein.\");\r\n     }\r\n     catch (Exception e) {\r\n         Console.WriteLine(\"Fehler: {0}\", e.GetBaseException().ToString());\r\n     }\r\n     \/\/Restarbeiten\r\n     Console.Write(\"Restarbeiten.\");\r\n     Console.ReadLine();\r\n}<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 7: Restarbeiten nach Auftreten eines Fehlers am Ende der Methode<\/span><\/b><\/p>\n<p>Was aber, wenn wir die Methode beim Auftreten eines Fehlers verlassen m&ouml;chten, weil beispielsweise die Daten f&uuml;r die noch auszuf&uuml;hrenden Anweisungen fehlen und dennoch Restarbeiten innerhalb der Methode n&ouml;tig sind?<\/p>\n<p>In diesem Fall kommt die der <b>finally<\/b>-Block einer Ausnahmebehandlung zum Zuge. Der Aufbau sieht dann grob so aus:<\/p>\n<pre>try {\r\n     ...\r\n}\r\ncatch {\r\n     ...\r\n}\r\nfinally {\r\n     ...\r\n}\r\n...Abschlie&szlig;ende Anweisungen<\/pre>\n<p>Die Anweisungen im <b>try<\/b>-Block werden so lange ausgef&uuml;hrt, bis ein Fehler auftaucht. Der <b>catch<\/b>-Block wird ausgel&ouml;st, sobald im <b>try<\/b>-Block ein Fehler ausgel&ouml;st wurde (zumindest, wenn es sich um einen allgemeinen <b>catch<\/b>-Block handelt).<\/p>\n<p>Der <b>finally<\/b>-Block schlie&szlig;lich wird immer ausgel&ouml;st &#8211; also unabh&auml;ngig davon, ob ein Fehler auftritt oder nicht.<\/p>\n<p>Was aber ist nun der Unterschied, ob ich Anweisungen in den <b>finally<\/b>-Block schreibe oder diese einfach an den letzten <b>catch<\/b>-Block anh&auml;nge? Ganz einfach: Wenn Sie im <b>catch<\/b>-Block die Methode verlassen, weil &#8211; wie oben beispielhaft angef&uuml;hrt &#8211; durch den Fehler eine weitere Ausf&uuml;hrung der Methode keinen Sinn macht, f&uuml;hrt die Methode dennoch den Teil im <b>finally<\/b>-Block aus, aber nicht mehr den im Anschluss an den letzten Block der Fehlerbehandlung.<\/p>\n<p>Ein Beispiel finden Sie in Listing 8. Geben Sie die Werte <b>1 <\/b>und <b>0 <\/b>f&uuml;r die beiden <b>ReadLine<\/b>-Anweisungen ein, l&ouml;st dies eine <b>DivideByZeroException <\/b>aus. Dabei soll die Methode nach der Ausgabe von <b>Der Dividend darf nicht 0 sein <\/b>mit der <b>return<\/b>-Anweisung verlassen werden. Dies geschieht auch, allerdings erst, nachdem die Anweisungen des <b>finally<\/b>-Blocks ausgef&uuml;hrt wurden.<\/p>\n<pre>public static void RestarbeitenMitFinally() {\r\n     try {\r\n         Console.WriteLine(\"Geben Sie den Dividend und den Divisor als ganze Zahlen ein.\");\r\n         Console.WriteLine(\"Dividend:\");\r\n         decimal Dividend = Convert.ToDecimal(Console.ReadLine());\r\n         Console.WriteLine(\"Divisor:\");\r\n         decimal Divisor = Convert.ToDecimal(Console.ReadLine());\r\n         decimal Quotient = Dividend \/ Divisor;\r\n         Console.WriteLine(\"Der Quotient lautet: {0}\", Quotient);\r\n     }\r\n     catch (DivideByZeroException) {\r\n         Console.WriteLine(\"Der Dividend darf nicht 0 sein.\");\r\n         return;\r\n     }\r\n     catch (Exception e) {\r\n         Console.WriteLine(\"Fehler: {0}\", e.GetBaseException().ToString());\r\n     }\r\n     finally {\r\n         \/\/Restarbeiten\r\n         Console.Write(\"Restarbeiten (Finally).\");\r\n         Console.ReadLine();\r\n     }\r\n     \/\/Restarbeiten\r\n     Console.Write(\"Restarbeiten.\");\r\n     Console.ReadLine();\r\n}<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 8: Restarbeiten nach Auftreten eines Fehlers am Ende der Methode mit Finally<\/span><\/b><\/p>\n<p>Wenn Sie hingegen einen Fehler ausl&ouml;sen, indem Sie eine Zeichenkette statt einer Zahl eingeben, l&ouml;st dies eine allgemeine Exception aus, f&uuml;r die der zweite <b>catch<\/b>-Block eingerichtet wurde. Nach diesem wird die Methode nicht verlassen, also geschieht Folgendes: Zun&auml;chst werden die Anweisungen im <b>finally<\/b>-Block ausgef&uuml;hrt und dann die Anweisungen hinter dem <b>try&#8230;catch&#8230;finally<\/b>-Block.<\/p>\n<h2>Fehler per Code ausl&ouml;sen<\/h2>\n<p>Unter VBA gab es die M&ouml;glichkeit, mit der <b>Error<\/b>-Anweisung einen Fehler auszul&ouml;sen. Die folgende Anweisung erzeugte dabei einen der eingebauten Fehler, wenn Sie die korrekte Fehlernummer angegeben haben &#8211; hier beispielsweise den Fehler <b>Division durch Null <\/b>(siehe Bild 10):<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2016_03\/pic_37_010.png\" alt=\"Erzeugen eines Fehlers unter VBA\" width=\"500\" height=\"302,2152\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 10: Erzeugen eines Fehlers unter VBA<\/span><\/b><\/p>\n<pre>Error 11<\/pre>\n<p>Unter C# gibt es nat&uuml;rlich auch eine M&ouml;glichkeit, selbst Fehler auszul&ouml;sen. Am einfachsten ist das, wenn Sie beim Auftreten einer behandelten Ausnahme doch noch die eingebaute Ausnahmebehandlung starten wollen. Dann f&uuml;gen Sie innerhalb des <b>catch<\/b>-Blocks einfach noch die Anweisung <b>throw (e) <\/b>ein (siehe Listing 9).<\/p>\n<pre>public static void ExceptionMitThrow() {\r\n     try {\r\n         ...\r\n         decimal Quotient = Dividend \/ Divisor;\r\n         Console.WriteLine(\"Der Quotient lautet: {0}\", Quotient);\r\n     }\r\n     catch (Exception e) {\r\n         Console.WriteLine(\"Es ist ein Fehler aufgetreten.\");\r\n         throw (e);\r\n     }\r\n}<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 9: Werfen des Fehlers nach der Fehlerbehandlung<\/span><\/b><\/p>\n<p>In der Ausf&uuml;hrung sieht das so wie in Bild 11 aus. Der Fehler wird ausgel&ouml;st und innerhalb des <b>catch<\/b>-Blocks behandelt, aber dort wird der Fehler erneut geworfen.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2016_03\/pic_37_011.png\" alt=\"Werfen des Fehlers trotz catch-Block\" width=\"500\" height=\"550,7145\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 11: Werfen des Fehlers trotz catch-Block<\/span><\/b><\/p>\n<p>Dieses Feature ist interessant, wenn Sie vor dem tats&auml;chlichen Verarbeiten der Ausnahme mit den daf&uuml;r vorgesehenen, eingebauten Mitteln noch benutzerdefinierte Schritte durchf&uuml;hren m&ouml;chten.<\/p>\n<h2>throw f&uuml;r benutzerdefinierte Fehler<\/h2>\n<p>Unter VBA konnten Sie, auch wenn dies den meisten VBA-Entwicklern nicht bekannt ist, benutzerdefinierte Fehler erzeugen. Dies gelingt auch unter C#. Hier m&uuml;ssen Sie allerdings zun&auml;chst eine benutzerdefinierte <b>Exception<\/b>-Klasse definieren, die von der Klasse <b>Exception<\/b> abgeleitet wird. Diese sieht im einfachsten Fall wie folgt aus:<\/p>\n<pre>public class MeineException : Exception {\r\n} <\/pre>\n<p>Wenn Sie diese <b>Exception<\/b>-Klasse nutzen wollen, erstellen Sie wie zuvor einen <b>try&#8230;catch<\/b>-Block. Um den Fehler auszul&ouml;sen, rufen Sie die <b>throw<\/b>-Methode auf und &uuml;bergeben dieser eine neue Instanz Ihrer benutzerdefinierten <b>Exception<\/b>-Klasse als Parameter (siehe Listing 10). Dies liefert eine &auml;hnliche Ausgabe, als wenn Sie eine der eingebauten Exceptions ausl&ouml;sen:<\/p>\n<pre>public static void Exception_Benutzerdefiniert() {\r\n     Console.WriteLine(\"Jetzt kommt ein Fehler.\");\r\n     try {\r\n         throw new MeineException();\r\n     }\r\n     catch(Exception e) {\r\n         Console.WriteLine(\"MeineException wurde ausgel&ouml;st ({0}).\", e.GetBaseException().ToString());\r\n     }\r\n}<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 10: Ausl&ouml;sen eines benutzerdefinierten Fehlers<\/span><\/b><\/p>\n<pre>Jetzt kommt ein Fehler.\r\nMeineException wurde ausgel&ouml;st (Fehlerbehandlung.MeineException: Eine Ausnahme vom Typ \"Fehlerbehandlung.MeineException\" wurde ausgel&ouml;st.\r\n    bei Fehlerbehandlung.Beispiele.Exception_Benutzerdefiniert() in C:\\Users\\....\\Fehlerbehandlung\\Fehlerbehandlung\\Beispiele.cs:Zeile 209.).\r\nAssembly: Fehlerbehandlung, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null<\/pre>\n<h2>Eigenschaften des Exception-Objekts<\/h2>\n<p>Das <b>Exception<\/b>-Objekt, das als Parameter eines <b>catch<\/b>-Blocks ermittelt werden kann, liefert einige Eigenschaften, die Sie in der Fehlerbehandlung nutzen k&ouml;nnen:<\/p>\n<ul>\n<li><b>Data<\/b>: Liefert eine Collection von Schl&uuml;ssel-Wert-Paaren mit weiteren Informationen zu Fehler.<\/li>\n<li><b>HelpLink<\/b>: Liefert einen Link zu einer Hilfedatei oder legt diesen fest.<\/li>\n<li><b>InnerException<\/b>: Ruft eine Exception-Instanz ab, welche die aktuelle Ausnahme ausgel&ouml;st hat.<\/li>\n<li><b>Message<\/b>: Liefert eine Meldung zur aktuellen Ausnahme.<\/li>\n<li><b>Source<\/b>: Name der Anwendung oder des Objekts, das die Ausnahme ausgel&ouml;st hat.<\/li>\n<li><b>StackTrace<\/b>: Liefert die Aufrufliste zum aktuellen Fehler.<\/li>\n<li><b>TargetSite<\/b>: Liefert die Methode, welche die Ausnahme ausgel&ouml;st hat.<\/li>\n<\/ul>\n<p>Die Methode aus Listing 11 gibt alle Informationen f&uuml;r einen einfachen Fehler aus. Das Ergebnis ist nicht besonders ergiebig:<\/p>\n<pre>public static void Exception_Eigenschaften() {\r\n     try {\r\n         decimal Null = 0;\r\n         Console.WriteLine(\"Eins durch Null: {0}\", 1 \/ Null);\r\n     }\r\n     catch (Exception e) {\r\n         Console.WriteLine(\"Data:\\n{0}\", e.Data.Count);\r\n         Console.WriteLine(\"HelpLink:\\n{0}\", e.HelpLink);\r\n         Console.WriteLine(\"HResult:\\n{0}\", e.HResult);\r\n         Console.WriteLine(\"InnerException:\\n{0}\", e.InnerException);\r\n         Console.WriteLine(\"Message:\\n{0}\", e.Message);\r\n         Console.WriteLine(\"Source:\\n{0}\", e.Source);\r\n         Console.WriteLine(\"StackTrace:\\n{0}\", e.StackTrace);\r\n         Console.WriteLine(\"TargetSite:\\n{0}\", e.TargetSite);\r\n     }\r\n}<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 11: Ausgabe der Fehlerinformationen des Exception-Objekts<\/span><\/b><\/p>\n<p><b>Data <\/b>liefert ein Objekt des Typs <b>System.Collections.ListDictionaryInternal<\/b>, wenn wir <b>Data.Count <\/b>ausgeben lassen, erhalten wir jedoch den Wert <b>0<\/b>, was darauf hindeutet, dass hier keine Name-Wert-Paare mit zus&auml;tzlichen Informationen hinterlegt wurden.<\/p>\n<p><b>Data <\/b>k&ouml;nnen Sie auch selbst f&uuml;llen, zum Beispiel, um weitere Informationen zu einem benutzerdefiniert ausgel&ouml;sten Fehler zu liefern.<\/p>\n<p>Die folgende Methode erzeugt im <b>try<\/b>-Block ein neues <b>Exception<\/b>-Objekt und f&uuml;gt zur <b>Data<\/b>-Auflistung mit der <b>Add<\/b>-Methode einen neuen Eintrag hinzu, dessen Name <b>Zeitpunkt <\/b>lautet und dessen Wert &uuml;ber den Ausdruck <b>DateTime.Now <\/b>mit Datum und Uhrzeit des Fehlers gef&uuml;llt wird. Danach l&ouml;sen wir den Fehler mit der <b>throw<\/b>-Anweisung aus.<\/p>\n<p>Der <b>catch<\/b>-Block greift das <b>Exception<\/b>-Objekt mit der Variablen <b>e <\/b>auf und gibt zun&auml;chst die Fehlermeldung aus der Eigenschaft <b>Message <\/b>und dann den Wert des Elements <b>Zeitpunkt <\/b>der <b>Data<\/b>-Auflistung aus:<\/p>\n<pre>public static void DataImBenutzerdefiniertenFehler() {\r\n     try {\r\n         Exception e = new Exception();\r\n         e.Data.Add(\"Zeitpunkt\", DateTime.Now);\r\n         throw e;\r\n     }\r\n     catch (Exception e) {\r\n         Console.WriteLine(\"Fehler: {0}\", e.Message);\r\n         Console.WriteLine(\"Zeitpunkt: {0}\", \r\n             e.Data[\"Zeitpunkt\"]);\r\n     }\r\n}<\/pre>\n<p>Dies ist das Ergebnis, das in der Konsole ausgegeben wird:<\/p>\n<pre>Fehler: Eine Ausnahme vom Typ \"System.Exception\" wurde ausgel&ouml;st.\r\nZeitpunkt: 06.06.2016 10:28:01<\/pre>\n<p>Wenn Sie mehrere Informationen mit dem Data-Objekt &uuml;bergeben m&ouml;chten, k&ouml;nnen Sie diese nat&uuml;rlich auch in einer foreach-Schleife durchlaufen. Wie dies aussieht, sehen Sie in Listing 12.<\/p>\n<pre>public static void MehrDataImBenutzerdefiniertenFehler() {\r\n     try {\r\n         Exception e = new Exception();\r\n         e.Data.Add(\"Zeitpunkt\", DateTime.Now);\r\n         e.Data.Add(\"Ort\", \"Duisburg\");\r\n         e.Data.Add(\"Benutzer\", \"Andr&eacute; Minhorst\");\r\n         throw e;\r\n     }\r\n     catch (Exception e) {\r\n         Console.WriteLine(\"Fehler: {0}\", e.Message);\r\n         if (e.Data.Count &gt; 0) {\r\n             Console.WriteLine(\"Inhalt von Data:\");\r\n             foreach (DictionaryEntry de in e.Data)\r\n                 Console.WriteLine(\"{0}: {1}\", de.Key, de.Value);\r\n         }\r\n     }\r\n}<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 12: Ausgabe des Data-Objekts einer Exception per foreach-Schleife<\/span><\/b><\/p>\n<p>Die Methode f&uuml;gt gleich drei verschiedene Name-Wert-Paare zur Data-Auflistung hinzu. Im catch-Zweig gibt die Methode wieder den Fehlertext aus. Danach pr&uuml;ft sie in einer if-Bedingung, ob das Data-Objekt &uuml;berhaupt einen Eintrag enth&auml;lt. Dies gelingt durch einen Vergleich der Eigenschaft Data.Count mit dem Wert 0. Liegen Eintr&auml;ge vor, startet eine foreach-Schleife, welche alle Elemente von e.Data, in diesem Fall mit dem Typ DictionaryEntry, durchl&auml;uft und jeweils mit der Variablen de referenziert. Innerhalb der Schleife gibt die Methode den Namen und den Wert des jeweiligen Eintrags aus.<\/p>\n<p>Die Ausgabe sieht nun so aus:<\/p>\n<pre>Inhalt von Data:\r\nZeitpunkt: 06.06.2016 10:41:00\r\nOrt: Duisburg\r\nBenutzer: Andr&eacute; Minhorst<\/pre>\n<h2>Eingebauten Fehler um Data anreichern<\/h2>\n<p><b>Data<\/b>-Elemente k&ouml;nnen Sie nicht nur benutzerdefinierten Fehlern hinzuf&uuml;gen, sondern nat&uuml;rlich auch eingebauten Exceptions. Wie das gelingt, zeigt das Beispiel aus Listing 13. Hier rufen wir im try-Block der ersten Methode eine untergeordnete Methode auf. Diese erzeugt in ihrem try-Block einen Fehler durch das Teilen durch 0. Der Fehler wird erst im catch-Block der untergeordneten Methode behandelt, wo wir diesem drei Name-Wert-Paare f&uuml;r die Data-Collection hinzuf&uuml;gen. Damit wir diese in der aufrufenden Methode auswerten k&ouml;nnen, werfen wir den Fehler mit der throw-Methode erneut. Die aufrufende Methode f&uuml;hrt dann ebenfalls den catch-Block aus und verarbeitet die Inhalte des Data-Objekts. Das Ergebnis sieht in diesem Falle &auml;hnlich aus wie zuvor:<\/p>\n<pre>public static void DataAnreichern() {\r\n     try {\r\n         DataAnreichernSub();\r\n     }\r\n     catch (Exception e) {\r\n         Console.WriteLine(\"Fehler: {0}\", e.Message);\r\n         if (e.Data.Count &gt; 0) {\r\n             Console.WriteLine(\"Inhalt von Data:\");\r\n             foreach (DictionaryEntry de in e.Data)\r\n                 Console.WriteLine(\"{0}: {1}\", de.Key, de.Value);\r\n         }\r\n     }\r\n}\r\nprivate static void DataAnreichernSub() {\r\n     try {\r\n         decimal Null = 0;\r\n         decimal EinsDurchNull = 1 \/ Null;\r\n     }\r\n     catch (Exception e) {\r\n         e.Data.Add(\"Zeitpunkt\", DateTime.Now);\r\n         e.Data.Add(\"Ort\", \"Duisburg\");\r\n         e.Data.Add(\"Benutzer\", \"Andr&eacute; Minhorst\");\r\n         throw e;\r\n     }\r\n}<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 13: Fehlerinformationen aus einer Sub-Methode anreichern<\/span><\/b><\/p>\n<pre>Fehler: Es wurde versucht, durch 0 (null) zu teilen.\r\nInhalt von Data:\r\nZeitpunkt: 06.06.2016 11:09:20\r\nOrt: Duisburg\r\nBenutzer: Andr&eacute; Minhorst<\/pre>\n<h2>HelpLink und HResult<\/h2>\n<p><b>HelpLink <\/b>liefert eine leere Zeichenkette, <b>HResult<\/b> den Wert <b>-2.147.352.558<\/b>. <b>HResult<\/b> liefert eine Fehlernummer, wie Sie sie auch von VBA kennen. Wer kennt nicht die Fehlernummer <b>3022 <\/b>f&uuml;r das Verletzen einer Integrit&auml;tsregel bei Hinzuf&uuml;gen oder &Auml;ndern eines Datensatzes?<\/p>\n<p>Im Falle des hier ausgel&ouml;sten Fehlers <b>DivideByZeroException <\/b>liefert <b>HResult <\/b>den Wert <b>-2.147.352.558<\/b>, hexadezimal <b>80020012<\/b>. Unter VBA h&auml;tten Sie diesen Wert zur Identifizierung des Fehlers in einer <b>Select Case<\/b>-Bedingung genutzt, unter C# ist dies nicht n&ouml;tig &#8211; hier k&ouml;nnen wir jede Exception direkt &uuml;ber den Typ in einem eigenen <b>catch<\/b>-Block verarbeiten.<\/p>\n<h2>InnerException<\/h2>\n<p><b>InnerException <\/b>liefert f&uuml;r das einfache Beispiel von oben eine leere Zeichenkette. Das hat seinen Grund, denn die <b>InnerException <\/b>bezeichnet eine Ausnahme, in deren Folge die aktuelle Ausnahme ausgel&ouml;st wurde. Ein Beispiel ist das F&uuml;llen einer Log-Datei mit den Informationen zu auftretenden Ausnahmen.<\/p>\n<p>Nun kann es geschehen, dass diese Log-Datei nicht beschrieben werden kann &#8211; beispielsweise durch fehlende Berechtigungen, weil die Datei oder das Verzeichnis vorhanden ist et cetera. Dann l&ouml;st dies eine weitere Ausnahme aus, die wir nat&uuml;rlich ebenfalls behandeln wollen, statt sie dem Benutzer als unbehandelte Ausnahme zu pr&auml;sentieren.<\/p>\n<p>Also packen wir wie in Listing 14 das <b>try&#8230;catch<\/b>-Konstrukt, das unseren Fehler durch das Dividieren von <b>1 <\/b>durch <b>0 <\/b>behandeln soll, in einen weiteren <b>try<\/b>-Block. Im inneren <b>try<\/b>-Block befinden sich die Anweisungen, die den <b>DivideByZero<\/b>-Fehler ausl&ouml;sen sollen.<\/p>\n<pre>public static void InnerException() {\r\n     try {\r\n         try {\r\n             decimal Null = 0;\r\n             decimal EinsDurchNull = 1 \/ Null;\r\n         }\r\n         catch (Exception e) {\r\n             if (File.Exists(\"c:<\/font>Logs<\/font>CSharplog.log\")== false) {\r\n                 throw new Exception(\"Log-Datei nicht gefunden\", e);\r\n             }\r\n         }\r\n     }\r\n     catch (Exception e) {\r\n         Console.WriteLine(\"Fehler: {0}\", e.Message);\r\n         Console.WriteLine(\"InnerException: {0}\", e.InnerException);\r\n     }\r\n}<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 14: Beispiel f&uuml;r das InnerException-Objekt<\/span><\/b><\/p>\n<p>Tritt nun die 1\/0-Exception auf, tritt der innere <b>catch<\/b>-Block auf den Plan: Er pr&uuml;ft, ob die Datei vorhanden ist, und wirft anderenfalls eine neue Ausnahme. Dieser &uuml;bergibt er als ersten Parameter den Text <b>Log-Datei nicht gefunden <\/b>und als zweiten mit der Variablen <b>e <\/b>einen Verweis auf die ausl&ouml;sende Exception.<\/p>\n<p>Dies bewirkt wiederum, dass der <b>catch<\/b>-Block des &auml;u&szlig;eren <b>try&#8230;catch<\/b>-Konstrukts angesteuert wird. Dieser gibt zuerst die Meldung des Fehlers und als zweites den Wert der Variablen <b>InnerException <\/b>in der Konsole aus:<\/p>\n<pre>Fehler: Log-Datei nicht gefunden\r\nInnerException: System.DivideByZeroException: Es wurde versucht, durch 0 (null) zu teilen.\r\n    bei System.Decimal.FCallDivide(Decimal& d1, Decimal& d2)\r\n    bei System.Decimal.op_Division(Decimal d1, Decimal d2)\r\n    bei Fehlerbehandlung.Beispiele.InnerException() in \r\n    C:\\...\\Fehlerbehandlung\\Beispiele.cs:Zeile 296.<\/pre>\n<h2>Fehlermeldung im Klartext<\/h2>\n<p><b>Message <\/b>liefert die Fehlermeldung im Klartext, in diesem Fall <b>Es wurde versucht, durch 0 (null) zu teilen.<\/b><\/p>\n<h2>Fehlerquelle<\/h2>\n<p><b>Source <\/b>bringt in diesem Fall den Namen der Klasse, die den Fehler ausgel&ouml;st hat: <b>mscorlib<\/b>.<\/p>\n<h2>Fehlerursprung verfolgen<\/h2>\n<p>Manchmal wird ein Fehler aus einer untergeordneten Methode in einer &uuml;bergeordneten Methode verarbeitet. Dann kann es hilfreich sein, den Weg hin zum Fehlerursprung zu verfolgen. Dabei hilft die Eigenschaft <b>StackTrace <\/b>des <b>Exception<\/b>-Objekts. In Listing 15 haben wir eine Fehlerbehandlung in einer Methode untergebracht, die eine weitere Methode aufruft, die dann schlie&szlig;lich die fehlerausl&ouml;sende Methode nutzt.<\/p>\n<pre>public static void StackTraceBeispiel() {\r\n     try {\r\n         StackTraceSub();\r\n     }\r\n     catch(Exception e) {\r\n         Console.WriteLine(\"Stacktrace:\\n{0}\", e.StackTrace);\r\n     }\r\n}\r\nprivate static void StackTraceSub() {\r\n     StackTraceSub2();\r\n}\r\nprivate static void StackTraceSub2() {\r\n     decimal Null = 0;\r\n     decimal EinsDurchNull = 1 \/ Null;\r\n}<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 15: Beispiel f&uuml;r die Ausgabe des StackTrace<\/span><\/b><\/p>\n<p>Das Ergebnis der <b>StackTrace<\/b>-Eigenschaft sieht dann wie folgt aus:<\/p>\n<pre>Stacktrace:\r\n    bei System.Decimal.FCallDivide(Decimal& d1, Decimal& d2)\r\n    bei System.Decimal.op_Division(Decimal d1, Decimal d2)\r\n    bei Fehlerbehandlung.Beispiele.StackTraceSub2() in \r\n        C:\\...\\Fehlerbehandlung\\Beispiele.cs:Zeile 322.\r\n    bei Fehlerbehandlung.Beispiele.StackTraceSub() in \r\n        C:\\...\\Fehlerbehandlung\\Beispiele.cs:Zeile 318.\r\n    bei Fehlerbehandlung.Beispiele.StackTraceBeispiel() in\r\n        C:\\...\\Fehlerbehandlung\\Beispiele.cs:Zeile 311.<\/pre>\n<p>Die oberen beiden Zeilen liefern Informationen &uuml;ber die Elemente des .NET-Frame, die den Fehler ausl&ouml;sten, die unteren drei beschreiben den Weg &uuml;ber die drei Methoden bis zum Fehler &#8211; die zuletzt aufgerufenen Methoden landen weiter oben in der Liste.<\/p>\n<h2>Fehlerhafte Methode untersuchen<\/h2>\n<p><b>TargetSite<\/b> liefert Informationen &uuml;ber die Methode, die den Fehler ausgel&ouml;st hat. Das Objekt enth&auml;lt selbst einige Eigenschaften mit weiteren Details, die wir an dieser Stelle nicht weiter inspizieren wollen.<\/p>\n<h2>Klassenweise Exceptions<\/h2>\n<p>Die Definition der verschiedenen Exceptions finden Sie &uuml;brigens &uuml;ber verschiedene Klassen des .NET-Frameworks verteilt. Das Basis-Objekt <b>Exception<\/b>, von dem alle &uuml;brigen <b>&#8230;Exception<\/b>-Objekte erben, befindet sich in der <b>System<\/b>-Klasse, so wie auch einige andere wie das hier oft verwendete <b>DivideByZero<\/b>-Objekt. Andere befinden sich in weiteren Klassen, zum Beispiel finden Sie <b>FileNotFoundException <\/b>in der Klasse <b>System.IO<\/b>.<\/p>\n<p>Wenn Sie also einmal ein <b>&#8230;Exception<\/b>-Objekt aus einem der Beispiele zu diesem Artikel eingeben m&ouml;chten und dieses nicht per IntelliSense angeboten wird, liegt das m&ouml;glicherweise daran, dass die Klasse, in der sich das Objekt befindet, noch nicht per <b>using<\/b>-Direktive hinzugef&uuml;gt wurde.<\/p>\n<h2>Zusammenfassung und Ausblick<\/h2>\n<p>Dies war ein &Uuml;berblick &uuml;ber die grundlegende Behandlung von Fehlern unter C# mit <b>try<\/b>-, <b>catch<\/b>&#8211; und <b>finally<\/b>-Bl&ouml;cken. Neben den Objekten und Eigenschaften, die Sie ben&ouml;tigen, um dem Benutzer statt unbehandelter Ausnahmen zumindest eine benutzerdefinierte Fehlermeldung zu liefern, haben Sie auch noch einige Eigenschaften kennen gelernt, die dem Entwickler bei der Fehleranalyse eine wichtige Hilfe sein k&ouml;nnen.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Nur wenige Access-Programmierer statten ihre Anwendungen mit einer ordentlichen Fehlerbehandlung aus. F&uuml;r viele ist dies ein leidiges Thema. Die Fehlerbehandlung sorgt im Optimalfall sowohl unter VBA als auch unter C# daf&uuml;r, dass eine Anwendung stabiler wird und nach dem Auftreten von Laufzeitfehlern nicht unerwartet reagiert oder sogar abst&uuml;rzt. W&auml;hrend es unter VBA nur wenige Konstrukte gibt, um eine Fehlerbehandlung zu implementieren, bietet C# schon eine Menge mehr. Dieser Artikel liefert eine Einf&uuml;hrung und zeigt, wie Sie die von VBA gewohnten Techniken unter C# einsetzen.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_uf_show_specific_survey":0,"_uf_disable_surveys":false,"footnotes":""},"categories":[662016,66032016,44000001,44000025],"tags":[],"yst_prominent_words":[],"class_list":["post-55000037","post","type-post","status-publish","format-standard","hentry","category-662016","category-66032016","category-CGrundlagen","category-VBAProgrammierung"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/posts\/55000037","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/comments?post=55000037"}],"version-history":[{"count":0,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/posts\/55000037\/revisions"}],"wp:attachment":[{"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/media?parent=55000037"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/categories?post=55000037"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/tags?post=55000037"},{"taxonomy":"yst_prominent_words","embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/yst_prominent_words?post=55000037"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}