Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Vorwort zur 6. Auflage
1 Allgemeine Einführung in .NET
2 Grundlagen der Sprache C#
3 Das Klassendesign
4 Vererbung, Polymorphie und Interfaces
5 Delegates und Ereignisse
6 Strukturen und Enumerationen
7 Fehlerbehandlung und Debugging
8 Auflistungsklassen (Collections)
9 Generics – Generische Datentypen
10 Weitere C#-Sprachfeatures
11 LINQ
12 Arbeiten mit Dateien und Streams
13 Binäre Serialisierung
14 XML
15 Multithreading und die Task Parallel Library (TPL)
16 Einige wichtige .NET-Klassen
17 Projektmanagement und Visual Studio 2012
18 Einführung in die WPF und XAML
19 WPF-Layout-Container
20 Fenster in der WPF
21 WPF-Steuerelemente
22 Elementbindungen
23 Konzepte von WPF
24 Datenbindung
25 Weitere Möglichkeiten der Datenbindung
26 Dependency Properties
27 Ereignisse in der WPF
28 WPF-Commands
29 Benutzerdefinierte Controls
30 2D-Grafik
31 ADO.NET – Verbindungsorientierte Objekte
32 ADO.NET – Das Command-Objekt
33 ADO.NET – Der SqlDataAdapter
34 ADO.NET – Daten im lokalen Speicher
35 ADO.NET – Aktualisieren der Datenbank
36 Stark typisierte DataSets
37 Einführung in das ADO.NET Entity Framework
38 Datenabfragen des Entity Data Models (EDM)
39 Entitätsaktualisierung und Zustandsverwaltung
40 Konflikte behandeln
41 Plain Old CLR Objects (POCOs)
Stichwort

Download:
- Beispiele, ca. 62,4 MB

Buch bestellen
Ihre Meinung?

Spacer
Visual C# 2012 von Andreas Kühnel
Das umfassende Handbuch
Buch: Visual C# 2012

Visual C# 2012
Rheinwerk Computing
1402 S., 6., aktualisierte und erweiterte Auflage 2013, geb., mit DVD
49,90 Euro, ISBN 978-3-8362-1997-6
Pfeil 7 Fehlerbehandlung und Debugging
Pfeil 7.1 Laufzeitfehler behandeln
Pfeil 7.1.1 Laufzeitfehler erkennen
Pfeil 7.1.2 Die »try...catch«-Anweisung
Pfeil 7.1.3 Behandlung mehrerer Exceptions
Pfeil 7.1.4 Die Reihenfolge der »catch«-Zweige
Pfeil 7.1.5 Ausnahmen in einer Methodenaufrufkette
Pfeil 7.1.6 Ausnahmen werfen oder weiterleiten
Pfeil 7.1.7 Die »finally«-Anweisung
Pfeil 7.1.8 Die Klasse »Exception«
Pfeil 7.1.9 Benutzerdefinierte Ausnahmen
Pfeil 7.2 Debuggen mit Programmcode
Pfeil 7.2.1 Einführung
Pfeil 7.2.2 Die Klasse »Debug«
Pfeil 7.2.3 Die Klasse »Trace«
Pfeil 7.2.4 Bedingte Kompilierung
Pfeil 7.3 Fehlersuche mit Visual Studio 2012
Pfeil 7.3.1 Debuggen im Haltemodus
Pfeil 7.3.2 Das »Direktfenster«
Pfeil 7.3.3 Weitere Alternativen, um Variableninhalte zu prüfen

7 Fehlerbehandlung und DebuggingZur nächsten Überschrift


Galileo Computing - Zum Seitenanfang

7.1 Laufzeitfehler behandelnZur nächsten ÜberschriftZur vorigen Überschrift

Fast alle Beispiele dieses Buches waren bisher so angelegt, als könnte nie ein Fehler auftreten. Aber Ihnen ist es beim Testen eines Beispielcodes sicherlich schon passiert, dass Sie anstatt einer Zahl einen Buchstaben eingegeben haben oder umgekehrt – genau entgegengesetzt zu dem, was das Programm in diesem Moment erwartete. Sie wurden danach mit einem Laufzeitfehler konfrontiert, was zur sofortigen Beendigung des Programms führte.

Dieser Umstand ist natürlich insbesondere dann unangenehm und inakzeptabel, wenn bei einem Endanwender ein solcher Fehler auftritt. Sollten diesem dann noch Daten unwiederbringlich verloren gegangen sein, ist der Ärger vorprogrammiert. Sie haben einen unzufriedenen Kunden, der an Ihren Qualitäten als Entwickler zweifelt, und anschließend noch die undankbare Aufgabe, den oder gar die Fehler zu lokalisieren und in Zukunft auszuschließen.

Welcher Entwickler kann zuverlässig voraussehen, welche Eingabe ein Anwender tätigt und vielleicht gar noch in welcher Reihenfolge, wenn er die grafische Benutzeroberfläche einer Applikation bedient? Welcher Anwender kann nach einem Fehler genau sagen, welche Arbeitsschritte und Eingaben zu der Fehlerauslösung geführt haben, welche Programme er über das Internet installiert hat usw.? Anwender sind fehlerfrei, sie machen alles richtig, nur das Programm ist schlecht. Seien wir doch einmal ehrlich zu uns selbst: Gibt es ein Softwarehaus, das von sich selbst behaupten kann, unter der Last des Termindrucks nicht schon mindestens einmal ein Programm ausgeliefert zu haben, das eine unzureichende Testphase durchlaufen hat?

Es gibt aber auch eine Fehlergattung, die nicht das unplanmäßige Beenden des Programms nach sich zieht, sondern nur falsche Ergebnisse liefert: die logischen Fehler. Dies ist deshalb sehr unangenehm, weil solche Fehler oft sehr spät erkannt werden und weitreichende Konsequenzen haben können. Denken Sie einmal daran, welche Auswirkungen es haben könnte, wenn ein Finanz- und Buchhaltungsprogramm (FIBU) einen falschen Verkaufspreis ermitteln würde. Es kommt nicht zu einem Laufzeitfehler, der anzeigt, dass etwas nicht richtig abläuft. Solche Fehler können im schlimmsten Fall sogar die Existenz eines gesamten Unternehmens gefährden. Um dieses Dilemma zu vermeiden, muss die Software ausgiebig getestet werden, wobei der Debugger der Entwicklungsumgebung wesentliche Unterstützung bietet und somit das wichtigste Hilfsmittel ist.

In diesem Abschnitt wollen wir uns mit der Fehlergattung auseinandersetzen, die zum Auslösen einer Ausnahme zur Laufzeit führt und die verschiedensten Ursachen haben kann:

  • Anwender geben unzulässige Werte ein.
  • Es wird versucht, eine nicht vorhandene Datei zu öffnen.
  • Es wird versucht, eine Division durch »0« durchzuführen.
  • Beim Zugriff auf eine Objektmethode ist der Bezeichner der Objektvariablen noch nicht initialisiert.
  • Eine Netzwerkverbindung ist instabil.
  • ...

Die Liste ist schier endlos lang. Aber allen Fehlern ist eines gemeinsam: Sie führen zum Absturz des Programms, wenn der auftretende Fehler nicht behandelt wird.


Galileo Computing - Zum Seitenanfang

7.1.1 Laufzeitfehler erkennenZur nächsten ÜberschriftZur vorigen Überschrift

Das folgende Listing demonstriert einen typischen Laufzeitfehler und die daraus resultierenden Konsequenzen. Die Aufgabe, die das Programm ausführen soll, ist dabei simpel: Es soll eine Textdatei öffnen und deren Inhalt in die Konsole schreiben.

using System.IO;

class Program {
static void Main(string[] args)
{
StreamReader stream = new StreamReader(@"C:\Text.txt");
Console.WriteLine(stream.ReadToEnd());

stream.Close();
Console.ReadLine();
}
}

Listing 7.1 Öffnen und Lesen einer Textdatei

Beachten Sie bitte, dass ein Backslash in einer Zeichenfolge als Escape-Sequenz interpretiert wird. Um diese Interpretation aufzuheben, geben Sie entweder zwei aufeinanderfolgende Backslashs an oder stellen Sie, wie oben gezeigt, der Zeichenfolge ein »@«-Zeichen voran.

Die Klassenbibliothek des .NET Frameworks bietet zum Öffnen einer Textdatei die Klasse StreamReader im Namespace System.IO an. Einer der Konstruktoren dieser Klasse erwartet den vollständigen Pfad zu der zu öffnenden Datei:

public StreamReader(string path);

Aus dem Datenstrom können mit Read einzelne Zeichen gelesen werden, mit ReadLine eine komplette Zeile. ReadToEnd hingegen liest den ganzen Datenstrom vom ersten bis zum letzten Zeichen. Im Beispiel wird die letztgenannte Methode benutzt und die Rückgabe aus dem Datenstrom als Argument der WriteLine-Methode der Console übergeben.

Solange die angegebene Datei existiert, wird die Anwendung fehlerfrei ausgeführt. Wenn Sie dem Konstruktor der Klasse StreamReader allerdings eine Zeichenfolge auf eine nicht vorhandene Datei übergeben, wird die Laufzeit der Anwendung mit einer Ausnahme (Exception) beendet und eine Fehlermeldung angezeigt (siehe Abbildung 7.1).

Abbildung

Abbildung 7.1 Anzeige der Exception in Visual Studio 2012

Sollte Ihnen der Hinweis auf die Ursache der Ausnahme nicht ausreichen, können Sie sich auch weitere Details dazu anzeigen lassen. Klicken Sie dazu auf den Link Details anzeigen im Ausnahmefenster. Daraufhin öffnet sich ein Dialog, dem Sie möglicherweise weitere interessante Details im Zusammenhang mit der Exception entnehmen können (siehe Abbildung 7.2).

Abbildung

Abbildung 7.2 Details einer Exception

Fehler dieser Art müssen schon während der Programmierung erkannt und behandelt werden. Die Fehlerbehandlung hat die Zielsetzung, dem Anwender beispielsweise durch eine Eingabekorrektur die Fortsetzung des Programms zu ermöglichen oder – schlimmstenfalls – zumindest alle notwendigen Daten zu sichern, bevor das Programm ordentlich beendet wird.


Galileo Computing - Zum Seitenanfang

7.1.2 Die »try...catch«-AnweisungZur nächsten ÜberschriftZur vorigen Überschrift

Ein Programm wird sofort unplanmäßig beendet, wenn eine nicht behandelte Exception auftritt. Um auf eine auftretende Ausnahme zu reagieren und diese zu behandeln, benutzen Sie die try-catch-Syntax, die wir uns nun zunächst in ihrer einfachsten Form ansehen wollen.

try {
[...]
}
catch
(Ausnahmetyp) {
[...]
}
[...]

Der try-Block beinhaltet zumindest die Anweisungen, die potenziell eine Ausnahme verursachen können. Tritt kein Laufzeitfehler auf, werden alle Anweisungen im try-Block ausgeführt. Danach setzt das Programm hinter dem catch-Block seine Arbeit fort. Verursacht eine der Anweisungen innerhalb des try-Blocks jedoch einen Fehler, werden alle folgenden Anweisungen innerhalb dieses Blocks ignoriert, und der Programmablauf führt den Code im catch-Anweisungsblock aus. Hier könnten beispielsweise Benutzereingaben gesichert oder Netzwerkverbindungen getrennt werden. Oft werden hier auch die Details der ausgelösten Ausnahme protokolliert. Nach der Abarbeitung des catch-Blocks wird das Programm mit der Anweisung fortgesetzt, die dem catch-Anweisungsblock folgt.

Eine Ausnahme wird in einer OOP-Umgebung durch ein Objekt beschrieben. Im allgemeinsten Fall ist das der Typ Exception, der als Parametertyp des catch-Zweigs anzugeben ist. Es sei schon an dieser Stelle darauf hingewiesen, dass es sehr viele spezialisierte Ausnahmen gibt, um auf einen bestimmten Fehler spezifisch reagieren zu können.

Greifen wir noch einmal auf das Beispiel am Anfang dieses Kapitels zurück, in dem eine Datei geöffnet und an der Konsole ausgegeben werden soll. Das Beispiel soll nun um eine passende Ausnahmebehandlung ergänzt werden.

class Program {
static void Main(string[] args)
{
StreamReader stream = null;
Console.Write("Welche Datei soll geöffnet werden? ... ");
string path = Console.ReadLine();

// Fehlerbehandlung einleiten

try {

// die folgende Anweisung kann zu einer Exception führen

stream = new StreamReader(path);
Console.WriteLine(stream.ReadToEnd());
stream.Close();
}
catch(Exception ex) {

// Ausgabe einer Fehlermeldung

Console.WriteLine(ex.Message);
}
Console.WriteLine("Nach der Exception-Behandlung");
Console.ReadLine();
}
}

Listing 7.2 Komplette Fehlerbehandlung zum Öffnen einer Datei

Starten Sie das Programm, und geben Sie nach der Aufforderung einen gültigen Zugriffspfad an, wird die Datei geöffnet und der Inhalt an der Konsole angezeigt. Das Programm wird bis zum catch-Statement ausgeführt und verzweigt danach zu der Anweisung, die dem catch-Block folgt, was durch eine Konsolenausgabe bestätigt wird.

Das ist der Normalfall – oder ist vielleicht eher eine falsche Benutzereingabe als normal anzusehen? Wie dem auch sei, unser kleines Programm ist in der Lage, auch damit umzugehen. Die Anweisung, die eine Ausnahme im obigen Beispiel auslösen könnte, ist anscheinend der Aufruf des Konstruktors der Klasse StreamReader, dem eine Pfadangabe als Argument übergeben wird:

stream = new StreamReader(str);

Bei einer Ausnahme verzweigt der Programmablauf in den catch-Block und führt die darin enthaltenen Anweisungen aus. Häufig wird man hier die Eigenschaft Message des Exception-Objekts abfragen, die eine benutzerfreundliche Fehlerbeschreibung liefert, z. B.:

Console.WriteLine(ex.Message);

Nach der Ausführung des catch-Blocks wird das Programm ordnungsgemäß mit den sich daran anschließenden Anweisungen fortgesetzt. Damit haben wir unser Ziel erreicht: Obwohl ein Laufzeitfehler aufgetreten ist, kontrollieren wir weiterhin das Laufzeitverhalten.

Hinweis

Sie müssen nicht unbedingt dem catch-Zweig eine Exception angeben, wie das folgende Codefragment zeigt:

catch
{
[...]
}

Auch wenn die Variante jede Ausnahme abfängt und behandelt, können ausnahmespezifische Informationen nicht ausgewertet werden. Daher eignet sich diese allgemeine Form nur in wenigen Fällen und sollte in der Regel vermieden werden.


Galileo Computing - Zum Seitenanfang

7.1.3 Behandlung mehrerer ExceptionsZur nächsten ÜberschriftZur vorigen Überschrift

Der Grund für eine Ausnahme kann vielfältig sein. Beispielsweise kann in unserem Listing bei dem Versuch, eine Datei zu öffnen, ein falscher Dateiname oder ein nicht vorhandenes Verzeichnis angegeben werden. Oder es wird eine leere Zeichenfolge übergeben. Alle diese Fehler lösen unterschiedliche Exceptions aus.

Vielleicht werden Sie sich die Frage stellen, woher die Kenntnis stammt, welche Ausnahmen beim Aufruf des Konstruktors der Klasse StreamReader zumindest theoretisch ausgelöst werden können. Die Antwort ist sehr einfach: Die Angaben sind in der Dokumentation der entsprechenden Klasse zu finden. Ein Blick in die Dokumentation des in unserem Beispiel eingesetzten StreamReader-Konstruktors verrät, dass dieser fünf unterschiedliche Ausnahmen auslösen kann:

  • ArgumentException
  • ArgumentNullException
  • FileNotFoundException
  • DirectoryNotFoundException
  • IOException

Die Ausnahme ArgumentException wird ausgelöst, wenn der Anwender an der Konsole nach der Aufforderung zur Eingabe des Pfades keine Angabe macht und das Programm fortsetzt. Eine ähnliche Ausnahme, ArgumentNullException, würde bei der Übergabe eines nichtinitialisierten Strings auftreten:

string path = null;
StreamReader dataStream = new StreamReader(path);

Geben Sie einen nicht existenten Datei- oder Ordnernamen ein, kommt es zu einer Ausnahme vom Typ FileNotFoundException bzw. DirectoryNotFoundException. Der letzten in der Dokumentation aufgeführten Ausnahme, IOException, kommt eine besondere Bedeutung zu, der wir uns gleich widmen werden.

Egal welchen Fehler Sie im Beispielcode oben auch provozieren, er wird immer behandelt. Das hängt damit zusammen, dass alle Ausnahmen durch Klassen beschrieben werden, die auf die gemeinsame Basis Exception zurückzuführen sind (siehe Abbildung 7.3). Damit finden alle Ausnahmen im catch-Zweig mit dem Parametertyp Exception eine passende Behandlungsroutine.

Abbildung

Abbildung 7.3 Die Hierarchie der Exceptions (Auszug)

Nur einen catch-Zweig zu codieren hat einen Nachteil, wenn man auf bestimmte Ausnahmen speziell reagieren möchte. Um auf verschiedene Ausnahmen spezifisch reagieren zu können, geben wir mehrere catch-Anweisungsblöcke an, von denen jeder auf einen bestimmten Ausnahmetyp reagiert.

// Beispiel: ..\Kapitel 7\TryCatchSample 

class Program {
static void Main(string[] args) {
StreamReader stream = null;
Console.Write("Welche Datei soll geöffnet werden? ... ");
string path = Console.ReadLine();
try {
stream = new StreamReader(path);
Console.WriteLine("--- Dateianfang ---");
Console.WriteLine(stream.ReadToEnd());
Console.WriteLine("--- Dateiende -----");
stream.Close();
}

// Datei nicht gefunden

catch (FileNotFoundException ex) {
Console.WriteLine(ex.Message);
}

// Verzeichnis existiert nicht

catch (DirectoryNotFoundException ex) {
Console.WriteLine(ex.Message);
}

// Pfadangabe war 'null'

catch (ArgumentNullException ex) {
Console.WriteLine(ex.Message);
}

// Pfadangabe war leer ("")

catch (ArgumentException ex) {
Console.WriteLine(ex.Message);
}

// allgemeine Exception

catch (Exception ex) {
Console.WriteLine(ex.Message);
}
Console.WriteLine("Nach der Exception-Behandlung");
Console.ReadLine();
}
}

Listing 7.3 Beispielprogramm mit detaillierter Fehleranalyse

Jeder catch-Zweig beschreibt nun einen bestimmten Ausnahmetyp. Beim Auftreten einer Ausnahme werden die catch-Zweige so lange der Reihe nach angesteuert, bis der Typ gefunden wird, der die ausgelöste Ausnahme beschreibt. Anschließend wird der Programmablauf mit den Anweisungen fortgesetzt, die sich hinter dem letzten catch-Zweig befinden.

Im Beispiel oben wird demnach zuerst geprüft, ob der Exception eine nicht existierende Datei zugrunde liegt (FileNotFoundException). Hat die Ausnahme eine andere Ursache, wird geprüft, ob dem Konstruktor ein ungültiges Verzeichnis übergeben wurde (DirectoryNotFoundException). War das auch nicht der Fall, wird der aufgetretene Fehler mit ArgumentNullException verglichen. Das setzt sich so lange fort, bis möglicherweise auch noch der letzte catch-Zweig aufgerufen wird. Kann in diesem die Ausnahme auch nicht behandelt werden, gilt sie als unbehandelt und das Programm wird beendet.

Grundsätzlich plädiere ich dafür, in jeder Ausnahmebehandlung im letzten (oder vielleicht auch einzigen) catch-Zweig den Typ Exception anzugeben. Damit ist man als Entwickler immer auf der sicheren Seite, dass die Anwendung nicht unplanmäßig beendet wird (auch wenn der Anwender möglicherweise mit der Meldung »Unbekannter Fehler« konfrontiert werden muss). Verzichten wir auf den letzten catch-Zweig im Beispiel TryCatchSample, könnte nämlich trotz aller catch-Zweige immer noch eine Ausnahme auftreten. Es ist die Methode ReadToEnd, die eine OutOfMemoryException wirft, falls die Datei mangels Speicher nicht komplett eingelesen werden kann. Mal ganz ehrlich, hätten Sie daran gedacht?


Galileo Computing - Zum Seitenanfang

7.1.4 Die Reihenfolge der »catch«-ZweigeZur nächsten ÜberschriftZur vorigen Überschrift

Die Abarbeitung der catch-Zweige folgt dem »Ist-eine«-Prinzip der Vererbung. Daraus folgt, dass eine bestimmte Reihenfolge bei der Angabe der catch-Zweige eingehalten werden muss, und die lautet: Ausgehend vom ersten bis hin zum letzten catch-Zweig werden die angegebenen Ausnahmen immer allgemeiner. Sollten Sie diese Richtlinie nicht beachten, wird Visual Studio Sie darauf aufmerksam machen, weil dann Programmcode vorliegt, der nicht erreicht werden kann.


Galileo Computing - Zum Seitenanfang

7.1.5 Ausnahmen in einer MethodenaufrufketteZur nächsten ÜberschriftZur vorigen Überschrift

Eine Ausnahme muss in jedem Fall behandelt werden, um das laufende Programm vor dem Absturz zu bewahren. Kennzeichnend war bisher, dass wir eine Ausnahme in der Methode behandelten, in der sie auftrat. Das muss aber nicht unbedingt so sein.

Stellen Sie sich vor, der Code zum Öffnen einer Datei unseres Beispiels TryCatchSample wäre nicht in Main, sondern einer anderen Methode, nennen wir sie wieder DoSomething, implementiert. Tatsächlich muss ein etwaig auftretender Fehler nicht in DoSomething mit try-catch behandelt werden, es kann auch in der Methode Main geschehen, wie das folgende Codefragment zeigt:

static void Main(string[] args) {
try {
DoSomething();
}

// Behandlung der Ausnahme

catch (Exception ex) {
Console.WriteLine(ex.Message);
}
}

static void DoSomething() {

// hier wird eine Exception ausgelöst, die nicht behandelt wird

}

Listing 7.4 Laufzeitfehler in einem Aufrufstack

Wird aus einer Methode heraus (hier Main) eine zweite (hier DoSomething) aufgerufen und tritt in letztgenannter eine Ausnahme auf, sucht die Laufzeitumgebung zunächst in der fehlerauslösenden Methode nach einer passenden Ausnahmebehandlung. Ist hier keine implementiert oder wird die Ausnahme von keinem der catch-Zweige behandelt, wird die Ausnahme an den Aufrufer übergeben. Nimmt sich dieser des ausgelösten Fehlers an, ist den Anforderungen Genüge getan, und die Anwendung wird klaglos weiterlaufen, ansonsten gilt die Ausnahme als nicht behandelt und die Anwendung stürzt unweigerlich ab.

Die Methodenaufrufkette darf auch durchaus noch mehr Stationen haben. Wichtig ist nur, dass spätestens der Auslöser der Aufrufkette auf die Exception reagiert.


Galileo Computing - Zum Seitenanfang

7.1.6 Ausnahmen werfen oder weiterleitenZur nächsten ÜberschriftZur vorigen Überschrift

In der Praxis werden Sie häufig auf den Umstand treffen, dass in einer Komponente eine Ausnahme ausgelöst und mit try-catch behandelt wird, die Ausnahme aber dennoch an den Aufrufer weitergeleitet werden muss. Das ist häufig der Fall, wenn Sie dem Anwender nicht direkt aus der auslösenden Komponente eine Information zukommen lassen können.

Um eine Ausnahme an den Aufrufer weiterzuleiten oder ganz generell eine neue (auch benutzerdefinierte) Ausnahme auszulösen, wird das throw-Statement benutzt, z. B.:

throw new XyzExcpetion();

Wird in einem catch-Block mit throw eine Exception geworfen, muss diese vom Aufrufer der fehlerverursachenden Methode behandelt werden.


Galileo Computing - Zum Seitenanfang

7.1.7 Die »finally«-AnweisungZur nächsten ÜberschriftZur vorigen Überschrift

Die strukturierte Fehlerbehandlung bietet optional noch eine weitere, bislang noch nicht erwähnte Klausel an, in der unterschiedliche Aufgaben erledigt werden können: die finally-Klausel, die unmittelbar dem letzten catch-Block folgt, falls sie angegeben wird.

[...]
try {
[...]
}
catch(Exception ex) {
[...]
}
finally {
[...]
}
[...]

Listing 7.5 Fehlerbehandlung mit »finally«-Zweig

Folgende Begleitumstände führen zur Abarbeitung der Anweisungen im finally-Block:

  • Es wird keine Ausnahme ausgelöst: Der try-Block wird komplett abgearbeitet, danach verzweigt das Programm zur finally-Klausel und wird anschließend mit der Anweisung fortgesetzt, die dem finally-Anweisungsblock folgt.
  • Es tritt eine Exception auf: Von der fehlerauslösenden Codezeile im try-Block aus sucht die Laufzeitumgebung nach der passenden catch-Klausel, führt diese aus und verzweigt zur finally-Klausel. Anschließend wird die Anweisung ausgeführt, die dem finally-Anweisungsblock folgt.

Der finally-Block wird demnach in jedem Fall ausgeführt, unabhängig davon, ob eine Ausnahme aufgetreten ist oder nicht. Diese Feststellung gilt auch für alle Anweisungen, die dem finally-Block folgen.

Es gibt aber zwei Situationen, in denen der dem finally-Block folgende Code nicht mehr ausgeführt wird:

  • Nehmen wir an, dass Sie nach der Behandlung der Ausnahme im catch-Block die Methode verlassen wollen, weil die Anweisungen, die sich den catch-Blöcken anschließen, nicht ausgeführt werden sollen. Sie werden dann im catch-Anweisungsblock mit return die Methode verlassen, z. B.:
    catch(Exception ex) {
    [...]
    return;
    }
    In diesem Fall hat return aber nicht die durchschlagende Konsequenz, die wir bisher von diesem Statement gewohnt sind. Die Methode wird nämlich nicht sofort verlassen, sondern es wird zunächst nach dem optionalen finally-Block gesucht. Ist er vorhanden, wird er ausgeführt. Es kommt nicht mehr zu der Ausführung der Anweisungen, die dem finally-Block folgen.
  • Die zweite Situation tritt im Zusammenhang mit dem throw-Statement auf, das in einem catch-Block die Weiterleitung einer Exception erzwingt. Auch hierbei wird nicht sofort die Exception geworfen, sondern erst nachdem finally abgearbeitet worden ist.

finally gestattet es somit, diverse Operationen unabhängig davon auszuführen, ob eine Exception aufgetreten ist oder nicht. Dabei handelt es sich in der Regel um die Freigabe von Fremdressourcen, beispielsweise um das Schließen einer Datenbankverbindung oder um die Freigabe einer Datei. finally ist nur sinnvoll im Zusammenhang mit throw oder return in einem catch-Block, weil dann die dem finally folgenden Anweisungen nicht mehr ausgeführt werden.


Galileo Computing - Zum Seitenanfang

7.1.8 Die Klasse »Exception«Zur nächsten ÜberschriftZur vorigen Überschrift

Die Basisklasse aller Ausnahmen bildet die Klasse Exception, die zum Namespace System gehört. Grundsätzlich sind alle Ausnahmentypen auf diese Klasse zurückzuführen. Exception hat in der .NET-Klassenbibliothek nur zwei direkte Ableitungen, mit denen eine Unterscheidung zwischen system- und anwendungsdefinierten Ausnahmen vordefiniert wird:

  • Die von Exception abgeleitete Klasse SystemException beschreibt alle Ausnahmen, die im Zusammenhang mit der Common Language Runtime (CLR) stehen. Ausnahmen aus diesem Bereich lassen sich als schwerwiegende Ausnahmen interpretieren, die aber noch vom Programm behandelt werden können.
  • Die Klasse ApplicationException, die ebenfalls direkt von Exception abgeleitet ist, dient per Definition allen benutzerdefinierten Ausnahmeklassen als Basis. Allerdings wird diese »Vorschrift« inzwischen von Microsoft selbst aufgeweicht, weil erkannt worden ist, dass anwendungsspezifische Ausnahmen, die von ApplicationException abgeleitet sind, keinen Vorteil gegenüber den Ausnahmen haben, die Exception selbst ableiten.

In Abbildung 7.3 weiter oben ist der Zusammenhang zwischen Exception, SystemException und ApplicationException dargestellt.

Um den Code, der eine Ausnahme behandelt, mit möglichst vielen guten Informationen über die Ursache zu versorgen, stellt die Basis Exception eine Reihe von Eigenschaften bereit. In Tabelle 7.1 sind diese aufgeführt.

Tabelle 7.1 Die Eigenschaften der Basisklasse »Exception«

Eigenschaft Beschreibung

Data

Stellt zusätzliche Informationen zu der Ausnahme bereit.

HelpLink

Verweist auf eine Hilfedatei, die diese Ausnahme beschreibt.

InnerException

Falls bei der Behandlung einer Ausnahme eine weitere Exception ausgelöst wird, beschreibt diese Eigenschaft die neue (innere) Ausnahme.

Message

Liefert eine Zeichenfolge mit der Beschreibung des aktuellen Fehlers. Die Information sollte so formuliert sein, dass sie auch von einem Anwender verstanden werden kann.

Source

Liefert einen String zurück, der die Anwendung angibt, in der die Ausnahme ausgelöst worden ist.

StackTrace

Beschreibt in einer Zeichenfolge die aktuelle Aufrufreihenfolge aller Methoden.

TargetSite

Liefert zahlreiche Informationen zu der Methode, in der die Ausnahme ausgelöst worden ist.

Wir sollten uns die wichtigsten Eigenschaften nun etwas genauer ansehen.

Die Eigenschaft »Message«

Die wohl am häufigsten ausgewertete Eigenschaft einer Ausnahme ist Message. Diese Eigenschaft beschreibt dem Anwender in leicht verständlicher Form die Ursache der aufgetretenen Ausnahme. Message ist schreibgeschützt, so dass Sie ihr nicht direkt einen Wert zuweisen können. Der einzige Weg, der Ausnahme eine spezifische Beschreibung mit auf den Weg zu geben, führt über den Konstruktor der Klasse. In Abschnitt 7.1.9 wird Ihnen das gezeigt.

Die Eigenschaft »StackTrace«

Wie Sie wissen, muss eine Ausnahme nicht unbedingt in der Methode behandelt werden, in der die Ausnahme aufgetreten ist. Diesem Umstand trägt StackTrace Rechnung, denn diese Eigenschaft dokumentiert alle Methoden, die zum Zeitpunkt einer Ausnahme ausgeführt werden. An oberster Stelle ist dabei die Methode zu finden, die Auslöser der Exception ist.

Dazu ein einfaches Beispiel. Aus Main heraus wird die Methode DoSomething1 aufgerufen, die selbst DoSomething2 aufruft. In DoSomething2 wird eine Exception vom Typ ArgumentNullException ausgelöst. Behandelt wird die Ausnahme im Initiator Main.

// Beispiel: ..\Kapitel 7\StackTraceSample 

static void Main(string[] args) {
try {
DoSomething1();
}
catch (Exception ex) {
Console.WriteLine(ex.StackTrace);
}
Console.ReadLine();
}
static void DoSomething1()
{
DoSomething2();
}
static void DoSomething2()
{

// hier wird die Exception ausgelöst

throw new ArgumentNullException();
}

Listing 7.6 Beispiel zur Eigenschaft »StackTrace«

Die Ausgabe an der Konsole sehen Sie in Abbildung 7.4.

Abbildung

Abbildung 7.4 Die Ausgabe der Eigenschaft »StackTrace« des Beispielcodes

Die Eigenschaft »Data«

Die Eigenschaft Data ermöglicht, im Ausnahmeobjekt mehrere Zusatzinformationen an die Routine weiterzuleiten, die die Ausnahme behandelt. Die von Data beschriebenen Informationen werden an ein Objekt weitergereicht, das die Schnittstelle IDictionary implementiert. Um es genauer zu formulieren: Bei dem Objekt handelt es sich um eine Collection, ähnlich einem Array. Allerdings werden die Daten nicht indexbasiert gespeichert und ausgewertet, sondern mit Hilfe eines eindeutigen Keys, bei dem es sich meistens um eine Zeichenfolge handelt.

In Kapitel 8 werden wir uns mit den wichtigsten Collections noch genauer auseinandersetzen.

Einträge in die von Data referenzierte Liste erfolgen durch Aufruf der Methode Add. Dieser Methode wird zuerst der eindeutige Key genannt, danach der zu speichernde Wert.

Data soll nicht die Eigenschaft Message der Exception ersetzen, sondern dient vielmehr dazu, zusätzliche, meist detailliertere Informationen über die Ausnahme bereitzustellen. Data wird beispielsweise häufig dazu benutzt, um der Fehlerbehandlung mitzuteilen, wann die Ausnahme aufgetreten ist, gewissermaßen liefert Data dann einen TimeStamp. Auch das wollen wir uns an einem Beispiel ansehen.

// Beispiel: ..\Kapitel 7\DataSample 

static void Main(string[] args) {
try {
DoSomething();
}
catch(Exception ex) {
Console.WriteLine("Message: {0}", ex.Message);
Console.WriteLine("{0} {1}",ex.Data["Info"], ex.Data["Date"]);
}
Console.ReadLine();
}

static void DoSomething() {
Exception ex = new Exception();
ex.Data.Add("Info", "Datum/Zeit:");
ex.Data.Add("Date", DateTime.Now);
throw ex;
}

Listing 7.7 Die Eigenschaft »Data« der Klasse »Exception«

DoSomething hat hier die Aufgabe, eine Ausnahme auszulösen. Dazu wird ein Objekt der Klasse Exception erzeugt (es könnte aber auch ein beliebiger anderer Ausnahmetyp sein). Zwei zusätzliche Dateninformationen werden in den Keys Info und Date bereitgestellt. Die Namen der Keys sind frei gewählt. Während Info nur eine allgemeine Beschreibung beinhaltet, wird in Date das aktuelle Datum samt Uhrzeit gespeichert. Dazu wird die Eigenschaft Now der Klasse DateTime abgerufen. Beide Informationen stehen in der Fehlerbehandlung von Main zur Verfügung und werden auch ausgewertet.

Die Eigenschaft »TargetSite«

Die Eigenschaft TargetSite liefert zahlreiche Informationen über die Methode, die die Ausnahme verursacht hat. Dabei können Sie im Bedarfsfall sogar so weit gehen, sich Informationen über die Parameterliste und deren Typen, den Rückgabewert der Methode und vieles weitere zu besorgen. Im folgenden Codefragment wird das Beispiel des vorhergehenden Abschnitts zugrunde gelegt und der catch-Zweig in Main wie folgt geändert:

[...]
catch(Exception ex) {
Console.WriteLine(ex.TargetSite.Name);
Console.WriteLine(ex.TargetSite);
}
[...]

Listing 7.8 Weiter gehende Information mit der Eigenschaft »TargetSite«

In die Ausgabe der Konsole werden die folgenden Informationen geschrieben:

DoSomething
void DoSomething()

Die Informationen, die TargetSite liefern kann, sind noch deutlich vielfältiger, als unser Beispiel hier beschreibt. Sollten Sie sich dafür interessieren, lesen Sie bitte die Dokumentation.

Die Eigenschaft »HelpLink«

TargetSite, StackTrace und Data sind mit ihrem Informationsgehalt wohl eher dem Entwickler bei einer Fehleranalyse hilfreich, während die Eigenschaft Message per Definition dem Anwender eine leicht verständliche Fehlerbeschreibung liefert. Möchten Sie dem Anwender über Message hinaus zusätzliche Informationen bereitstellen, weisen Sie der Eigenschaft HelpLink eine URL zu, die die Adresse eines Dokuments mit den entsprechenden Zusatzinformationen beschreibt. Wie Sie HelpLink einsetzen, zeigt das folgende Listing.

try {
DoSomething();
}
catch (Exception e) {
Console.WriteLine("Mehr Infos unter '{0}'", e.HelpLink);
}

public static void DoSomething() {
Exception ex = new Exception();
ex.HelpLink = "http://www.Tollsoft.de/Error712.htm";
throw ex;
}

Listing 7.9 Mit »HelpLink« dem Anwender eine weitere Informationsquelle nennen

Innere Exceptions

Nehmen wir an, Sie möchten innerhalb eines catch-Blocks alle mit der Exception verbundenen Informationen in einer Datei protokollieren, beispielsweise in einer Datei mit dem Pfad C:\Log\Exception.txt. Das Schreiben in Dateien ist genauso wie das Lesen grundsätzlich immer mit einem Ausnahmerisiko behaftet, da in diesem Zusammenhang eine weitere Ausnahme ausgelöst werden könnte. Was ist, wenn das Verzeichnis nicht mehr existiert oder die Datei gelöscht worden ist? Sie müssen folglich im catch-Zweig, der die eigentlich aufgetretene Ausnahme behandelt, eine weitere, innere Ausnahmebehandlung codieren.

Tritt während einer Ausnahmebehandlung eine andere Ausnahme auf (im Allgemeinen als »innere Ausnahme« bezeichnet), kann die Ausnahmebehandlung als gescheitert angesehen werden. Die ursprüngliche Ausnahme muss erneut ausgelöst und an den Aufrufer weitergeleitet werden. Dabei sollte zusätzlich die innere Ausnahme angegeben werden. Dazu dient die Eigenschaft InnerException.

Sehen wir uns die Vorgehensweise an einem Codebeispiel an. Angenommen, es sei im Code versucht worden, durch die Zahl »0« zu dividieren. Den Gesetzen der gehobenen Mathematik nach ist das keine gültige mathematische Operation, und es wird die Ausnahme DivideByZeroException ausgelöst. Nehmen wir zudem an, wir möchten die Ausnahme protokollieren. Dabei müssen wir berücksichtigen, dass auch das Schreiben in die Protokolldatei zu einer Ausnahme führen könnte.

[...]
catch
(DivideByZeroException ex) {
try {
FileStream stream = File.Open(...);
[...]
}
catch(Exception ex2) {
throw new DivideByZeroException(ex.Message, ex2)
}
}
[...]

Listing 7.10 Auslösen einer inneren Ausnahme

Zur Beschreibung einer inneren Exception müssen Sie nur den passenden Konstruktor der entsprechenden Exception-Klasse aufrufen. Wie wir später noch sehen werden, sollte jede Exception-Klasse (mindestens) vier Konstruktoren aufweisen. Eine Überladung nimmt dabei neben der Fehlerbeschreibung der äußeren Ausnahme auch die Referenz auf die neue, innere Ausnahme entgegen. Sollte das Öffnen der Protokolldatei fehlschlagen, wird die ursprüngliche (äußere) Exception an den Aufrufer weitergeleitet, der dann über die Auswertung der Eigenschaft InnerException die Möglichkeit hat, auch die innere, tatsächliche Fehlerquelle auszuwerten.


Galileo Computing - Zum Seitenanfang

7.1.9 Benutzerdefinierte AusnahmenZur nächsten ÜberschriftZur vorigen Überschrift

Die .NET-Klassenbibliothek stellt sehr viele Ausnahmeklassen zur Verfügung, mit denen die üblichen Ausnahmen im Rahmen einer Anwendung abgedeckt werden. Sehr oft reichen die vordefinierten Exception-Klassen jedoch nicht aus, weil anwendungsspezifische Umstände eine spezielle Ausnahme erfordern. In solchen Fällen ist man gezwungen, eigene Ausnahmeklassen bereitzustellen, die den folgenden Regeln entsprechen sollten:

  • Leiten Sie Ihre benutzerdefinierte Ausnahme von der Klasse Exception oder ApplicationException ab. Die ursprüngliche Idee von Microsoft, dass ApplicationException die Basis aller benutzerdefinierten Ausnahmen darstellen soll, hat in der Praxis keine Vorteile gezeigt. Inzwischen empfiehlt auch Microsoft die Klasse Exception als Basis.
  • Der Bezeichner jeder Ausnahme sollte mit Exception enden. Das ist zwar keine zwingende Vorschrift, sondern nur eine Konvention. Aber sie hilft, den Code besser zu verstehen.
  • Sie sollten in jeder Ausnahmeklasse mindestens vier Konstruktoren vorsehen. Die Parameterlisten sollten dabei identisch mit den Parameterlisten der Konstruktoren von Exception sein. Natürlich können Sie darüber hinaus auch weitere Konstruktoren codieren.
  • Die Ausnahmeklasse sollte mit dem Attribut Serializable markiert werden, um die Ausnahme serialisierbar zu machen. (Anmerkung: Bisher haben wir über die Themen »Attribute« und den »Serialisierungsprozess« noch nicht gesprochen. Trotzdem gehört dieser Punkt unbedingt in die Liste der zu berücksichtigenden Kriterien.)

Benutzerdefinierte Ausnahmen im Projekt »GeometricObjects«

Wir wollen nun eine benutzerdefinierte Ausnahme an einem Beispiel entwickeln. Dazu benutzen wir das Beispiel der Circle-Klasse des Projekts GeometricObjects. Wie Sie sich sicherlich noch erinnern, hatten wir festgelegt, dass die Übergabe an die Eigenschaft Radius größer oder gleich 0 sein muss. Die Eigenschaftsmethode Radius der Klasse Circle löst das Ereignis InvalidMeasure aus, wenn versucht wird, dem Radius einen negativen Wert zuzuweisen.

Das ist definitiv keine gute Lösung, denn beim Auftreten eines Fehlers sollte der Aufrufer gezwungen sein, diesen zu behandeln. Auf einen Event zu reagieren ist hingegen nur eine Option, die wahrgenommen werden kann oder auch nicht. Es gibt noch einen zweiten Punkt, den wir berücksichtigen müssen. Betrachten Sie dazu ein Codefragment, in dem einem Circle-Objekt bei der Instanziierung ein negativer Radius übergeben wird:

Circle kreis = new Circle(-5);
kreis.InvalidMeasure += kreis_InvalidMeasure;
[...]

Listing 7.11 Registrieren des Ereignishandlers für den Event »InvalidMeasure«

Die Bindung des Ereignishandlers an das Ereignis erfolgt erst, nachdem der Konstruktoraufruf beendet ist. Folglich kann das Ereignis auch nicht während des Konstruktoraufrufs ausgelöst werden, und der Aufrufer erhält keine Informationen, dass der übergebene Wert nicht akzeptiert werden konnte.

Fehler sollten nicht zur Auslösung eines Ereignisses führen. Solche Lösungen sind nicht nur schlecht, sie sind sogar inakzeptabel. Ein Fehler muss immer das Auslösen einer Exception zur Folge haben, die behandelt werden muss. Allerdings, das sei an dieser Stelle bereits angedeutet, können Sie sehr wohl eine Kombination von Exception und Ereignis in Betracht ziehen. Dazu gibt es auch einige Beispiele in der .NET-Klassenbibliothek. Eine solche Lösung sei am Ende der Ausführungen auch unser Ziel. Doch der Reihe nach ...

Zuerst wollen wir eine eigene Ausnahme bereitstellen, die wir als InvalidMeasureException bezeichnen. Um allen denkbaren Szenarien im Umfeld einer Ausnahme und deren möglichen Ableitungen zu entsprechen, sollten sich die vier Konstruktoren der Klasse Exception auch in einer benutzerdefinierten Ausnahme wiederfinden. Visual Studio unterstützt Sie dabei mit einem Code-Snippet (siehe Abbildung 7.5).

Abbildung

Abbildung 7.5 Das Code-Snippet einer Ausnahme

Nachdem Sie das Snippet eingefügt und den Standardbezeichner in InvalidMeasureException umbenannt haben, steht das Gerüst der neuen Ausnahme. Es enthält vier Konstruktoren:

[Serializable]
public class InvalidMeasureException : Exception
{
public InvalidMeasureException() { }
public InvalidMeasureException(string message) : base(message) { }
public InvalidMeasureException(string message, Exception inner)
: base(message, inner) { }
protected InvalidMeasureException(SerializationInfo info,
StreamingContext context) : base(info, context) { }
}

Listing 7.12 Vom »Exception«-Snippet erzeugter Code (Bezeichner bereits angepasst)

Da in der Basisklasse Exception jeweils ein gleich parametrisierter Konstruktor definiert ist, werden die Parameter mit base an den gleich parametrisierten Konstruktor der Basisklasse weitergeleitet.

Neben dem parameterlosen Konstruktor enthält die Klassendefinition zwei Konstruktoren, die in ihrer Parameterliste einen Parameter namens message vom Typ String definieren. Hierbei handelt es sich um die Zeichenfolge, die bei der Ausnahmebehandlung mit der Eigenschaft Message abgerufen werden kann und eine kurze, leicht verständliche Beschreibung der Fehlerursache anzeigt.

Der vierte, mit protected gekennzeichnete Konstruktor dient zusammen mit dem Attribut [Serializable] der Objektserialisierung. Da weder das Thema der Attribute noch das der Serialisierung bisher behandelt worden ist, gehe ich auf diesen Konstruktor nicht näher ein. Um aber die Vollständigkeit unserer Klasse zu gewährleisten, ist der Konstruktor hier mit aufgeführt.

Es spricht nichts dagegen, die Klassendefinition noch um weitere Konstruktoren oder andere Member wie Eigenschaften und Methoden zu ergänzen. Das würde sich anbieten, wenn im Zusammenhang mit der Ausnahme weiter gehende Anforderungen gestellt werden.

Die Ausnahme InvalidMeasureException soll ausgelöst werden, wenn die Überprüfung in der Eigenschaft Radius die Unzulässigkeit des Wertes festgestellt hat. Nach dem aktuellen Stand der Klasse Circle wird immer noch ein Ereignis ausgelöst – eine nicht akzeptable Lösung. Allerdings wollen wir das Ereignis nicht durch die Exception ersetzen, sondern stellen die folgenden Anforderungen an den Code:

  • Die Ausnahme wird in jeden Fall ausgelöst, wenn ein unzulässiger Wert zugewiesen werden soll.
  • Registriert der aufrufende Code einen Ereignishandler für InvalidMeasure, muss die Ausnahme nicht behandelt werden. Stattdessen wird die Ausnahme in einer weiteren Eigenschaft des EventArgs-Objekts bereitgestellt.
  • Wird kein Ereignishandler registriert, muss die Ausnahme behandelt werden.

Diese Verhaltensweise, entweder ein Ereignis zu behandeln oder die ausgelöste Ausnahme, findet sich auch im .NET Framework wieder. Ein gutes Beispiel dafür ist in ADO.NET das Ereignis RowUpdated des DataAdapter-Objekts.

Um die drei Forderungen zu erfüllen, müssen wir im ersten Schritt die Klasse InvalidMeasureEventArgs überarbeiten. Sie wird um die schreibgeschützte Eigenschaft Error vom Typ Exception ergänzt. Außerdem erhält der Konstruktor einen dritten Parameter, der das Exception-Objekt entgegennimmt.

// Ergänzte und geänderte InvalidMeasureEventArgs-Klasse

public class InvalidMeasureEventArgs : EventArgs {

// Felder

private Exception _Error;

// Eigenschaften

public Exception Error {
get { return _Error; }
}

// Konstruktor

public InvalidMeasureEventArgs(int invalidMeasure,
string propertyName, Exception error) {
_InvalidMeasure = invalidMeasure;
_Error = error;
if (propertyName == "" || propertyName == null)
_PropertyName = "[unknown]";
else
_PropertyName = propertyName;
}
}

Listing 7.13 Die ergänzte Klasse »InvalidMeasureEventArgs«

Im nächsten Schritt müssen wir die Eigenschaft Radius anpassen. Ist der übergebene Wert unzulässig, wird zuerst ein Objekt der Ausnahme InvalidMeasureException erzeugt, ein Zeitstempel an die Eigenschaft Data übergeben und anschließend die geschützte Methode OnInvalidMeasure aufgerufen, die für die Auslösung des Ereignisses sorgt.

// Überarbeitete Eigenschaft

public virtual int Radius {
get { return _Radius; }

set {
if (value >= 0)
_Radius = value;
else
{
InvalidMeasureException ex = new InvalidMeasureException
("Ein Radius von " + value + " ist nicht zulässig.");
ex.Data.Add("Time", DateTime.Now);
OnInvalidMeasure(new InvalidMeasureEventArgs(value, "Radius", ex));
}
}
}

Listing 7.14 Änderung der Eigenschaft »Radius« in der Klasse »Circle«

In der Methode OnInvalidMeasure der Klasse GeometricObject wird noch der else-Zweig ergänzt, in dem die Ausnahme geworfen wird, falls kein Ereignishandler registriert ist.

protected void OnInvalidMeasure(InvalidMeasureEventArgs e){
if (InvalidMeasure != null)
InvalidMeasure(this, e);
else
throw e.Error;
}

Listing 7.15 Änderung der Methode »OnInvalidMeasure« in »GeometricObject«

Zum Schluss bleibt noch, sich vom Erfolg der Implementierung zu überzeugen. Dazu dient der folgende Beispielcode, in dem beide Varianten einem Test unterzogen werden.

class Program {

static void Main(string[] args) {
Circle kreis1 = null;
Circle kreis2 = null;
try {
kreis1 = new Circle();
kreis1.InvalidMeasure += kreis_InvalidMeasure;
kreis1.Radius = -100;
kreis2 = new Circle(-89);
kreis2.Radius = -9;
}
catch (InvalidMeasureException ex){
Console.WriteLine("Im Catch-Block: " + ex.Message);
}
Console.ReadLine();
}

// der Ereignishandler

static void kreis_InvalidMeasure(object sender, InvalidMeasureEventArgs e) {
Console.WriteLine("Ereignishandler: " + e.Error.Message);
}
}

Listing 7.16 Hauptprogramm zum Testen der Ausnahme

Das Objekt kreis1 registriert das Ereignis InvalidMeasure, während kreis2 auf diese Option verzichtet. Entsprechend wird auch auf die Ausnahme reagiert: Die Übergabe eines unzulässigen Radius an kreis1 führt zur Ausführung des Ereignishandlers, während das Objekt kreis2 die Exception behandeln muss.

In der Gesamtlösung des Beispiels GeometricObjects auf der Buch-DVD unter Beispiele\Kapitel 7\GeometricObjectsSolution_8 sind neben der Änderung an der Klasse Circle auch die entsprechenden Änderungen an der Klasse Rectangle vorgenommen worden.



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.

>> Zum Feedback-Formular
<< zurück
  Zum Katalog
Zum Katalog: Visual C# 2012

Visual C# 2012
Jetzt bestellen


 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Katalog: Professionell entwickeln mit Visual C# 2012






 Professionell
 entwickeln mit
 Visual C# 2012


Zum Katalog: Windows Presentation Foundation






 Windows Presentation
 Foundation


Zum Katalog: Schrödinger programmiert C++






 Schrödinger
 programmiert C++


Zum Katalog: C++ Handbuch






 C++ Handbuch


Zum Katalog: C/C++






 C/C++


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo





Copyright © Rheinwerk Verlag GmbH 2013
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Rheinwerk Computing]

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de