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

Inhaltsverzeichnis
Vorwort zur 5. Auflage
1 Allgemeine Einführung in .NET
2 Grundlagen der Sprache C#
3 Klassendesign
4 Vererbung, Polymorphie und Interfaces
5 Delegates und Ereignisse
6 Weitere .NET-Datentypen
7 Weitere Möglichkeiten von C#
8 Auflistungsklassen (Collections)
9 Fehlerbehandlung und Debugging
10 LINQ to Objects
11 Multithreading und die Task Parallel Library (TPL)
12 Arbeiten mit Dateien und Streams
13 Binäre Serialisierung
14 Einige wichtige .NET-Klassen
15 Projektmanagement und Visual Studio 2010
16 XML
17 WPF – Die Grundlagen
18 WPF-Containerelemente
19 WPF-Steuerelemente
20 Konzepte der WPF
21 Datenbindung
22 2D-Grafik
23 ADO.NET – verbindungsorientierte Objekte
24 ADO.NET – Das Command-Objekt
25 ADO.NET – Der SqlDataAdapter
26 ADO.NET – Daten im lokalen Speicher
27 ADO.NET – Aktualisieren der Datenbank
28 Stark typisierte DataSets
29 LINQ to SQL
30 Weitergabe von Anwendungen
Stichwort

Buch bestellen
Ihre Meinung?

Spacer
<< zurück
Visual C# 2010 von Andreas Kühnel
Das umfassende Handbuch
Buch: Visual C# 2010

Visual C# 2010
geb., mit DVD
1295 S., 49,90 Euro
Rheinwerk Computing
ISBN 978-3-8362-1552-7
Pfeil 9 Fehlerbehandlung und Debugging
Pfeil 9.1 Die Behandlung von Laufzeitfehlern
Pfeil 9.1.1 Laufzeitfehler erkennen
Pfeil 9.1.2 Die Behandlung von Exceptions
Pfeil 9.1.3 Die »try...catch«-Anweisung
Pfeil 9.1.4 Behandlung mehrerer Exceptions
Pfeil 9.1.5 Die »finally«-Anweisung
Pfeil 9.1.6 Das Weiterleiten von Ausnahmen
Pfeil 9.1.7 Die Hierarchie der Exceptions
Pfeil 9.1.8 Die Reihenfolge der »catch«-Klauseln
Pfeil 9.1.9 Die Basisklasse »Exception«
Pfeil 9.1.10 Benutzerdefinierte Ausnahmen
Pfeil 9.2 Debuggen mit Programmcode
Pfeil 9.2.1 Einführung
Pfeil 9.2.2 Die Klasse »Debug«
Pfeil 9.2.3 Die Klasse »Trace«
Pfeil 9.2.4 Ablaufverfolgung mit »TraceListener«-Objekten
Pfeil 9.2.5 Steuerung der Protokollierung mit Schaltern
Pfeil 9.2.6 Bedingte Kompilierung
Pfeil 9.3 Debuggen mit Visual Studio 2010
Pfeil 9.3.1 Debuggen im Haltemodus
Pfeil 9.3.2 Das Direktfenster
Pfeil 9.3.3 Weitere Alternativen, um Variableninhalte zu prüfen


Galileo Computing - Zum Seitenanfang

9.2 Debuggen mit Programmcode Zur nächsten ÜberschriftZur vorigen Überschrift


Galileo Computing - Zum Seitenanfang

9.2.1 Einführung Zur nächsten ÜberschriftZur vorigen Überschrift

In Abschnitt 9.1, »Die Behandlung von Laufzeitfehlern«, haben wir uns mit Fehlern beschäftigt, die nach der erfolgreichen Kompilierung zur Laufzeit auftreten können und, falls sie nicht behandelt werden, unweigerlich zum Absturz des Programms führen. Vielleicht noch schlimmer sind Fehler, die weder vom Compiler erkannt werden noch einen Laufzeitfehler verursachen. Es sind die logischen Fehler, die ein falsches oder zumindest unerwartetes Ergebnis zur Folge haben. Um logische Fehler aufzuspüren, muss die Anwendung unter Zuhilfenahme des integrierten Debuggers untersucht werden.

Das .NET Framework stellt Ihnen eine Reihe von Hilfsmitteln zur Verfügung, um den Programmcode zu debuggen. Die Spanne reicht von der einfachen Ausgabe von Meldungen im Ausgabe-Fenster bis zur Umleitung der Meldungen in eine Datei oder das Windows-Ereignisprotokoll. Dabei können Sie das Laufzeitverhalten einer Anwendung sowohl mit Programmcode als auch mit der Unterstützung von Visual Studio 2010 überprüfen. Wir werden in den nächsten Abschnitten auf alle Debugging-Techniken eingehen.


Galileo Computing - Zum Seitenanfang

9.2.2 Die Klasse »Debug« Zur nächsten ÜberschriftZur vorigen Überschrift

In den vorangegangenen Beispielen haben wir uns sehr häufig eines Kommandos bedient, um beispielsweise den Inhalt von Variablen zu überprüfen. Es war die Methode WriteLine der Klasse Console:


int value = 4711;
Console.WriteLine(value);

Diese Technik hat zur Folge, dass die Ausgabe an der Konsole unübersichtlich wird und zwischen den erforderlichen Programminformationen immer wieder Informationen zu finden sind, die nur dazu dienen, die Entwicklung zu unterstützen. Bevor ein solches Programm an den Kunden ausgeliefert wird, müssen die Testausgaben aus dem Programmcode gelöscht werden.

Die Entwicklungsumgebung bietet uns eine bessere Alternative an. Dazu wird die Ausgabe nicht in das Konsolenfenster geschrieben, sondern in das Ausgabe-Fenster von Visual Studio 2010. Standardmäßig wird dieses Fenster am unteren Rand der Entwicklungsumgebung angezeigt. Sie können es sich anzeigen lassen, indem Sie im Menü Ansicht den Menüpunkt Ausgabe wählen.

Abbildung 9.2 Das Fenster »Ausgabe«

Sie haben dieses Fenster möglicherweise schon häufig gesehen und aufmerksam seinen Inhalt gelesen, denn bei jeder Kompilierung werden hier Informationen ausgegeben, beispielsweise ob die Kompilierung fehlerfrei war. Das Ausgabe-Fenster zeigt uns aber nicht nur Informationen an, die der Compiler hineinschreibt; wir können auch eigene Meldungen in dieses Fenster umleiten.

Eine Debug-Information in das Ausgabe-Fenster zu schreiben, ist genauso einfach wie die Ausgabe an der Konsole. Wir müssen nur die Anweisung


Console.WriteLine("...");

durch


Debug.WriteLine("...");

ersetzen. Debug ist eine nicht ableitbare Klasse des Namespace System.Diagnostics, die ausschließlich statische Member bereitstellt. Sie sollten den Namespace mit using bekannt geben.

Die Methode Debug.WriteLine unterscheidet sich von der Methode Console.WriteLine dahingehend, dass sie keine Formatierungsmöglichkeiten erlaubt. Um mehrere Informationen in einer gemeinsamen Zeichenfolge unterzubringen, müssen Sie daher den Verknüpfungsoperator + benutzen:


Debug.WriteLine("Inhalt von value = " +  value);

Programmablaufinformationen anzeigen

Debug.WriteLine ist mehrfach überladen und kann ein Argument vom Typ string oder object entgegennehmen. Eine parameterlose Überladung gibt es nicht.


public static void WriteLine(object value);
public static void WriteLine(string message);

Optional können wir auch ein zweites string-Argument übergeben, das eine detaillierte Beschreibung bereitstellt, die vor der eigentlichen Debug-Information ausgegeben wird.


public static void WriteLine(object value, string category);
public static void WriteLine(string message, string category);

Sehen wir uns das an einem Beispiel an. Die Anweisung


Debug.WriteLine("Inhalt von value = " + value, "Variable value");

wird in das Ausgabe-Fenster


Variable value: Inhalt von value = 34

schreiben – vorausgesetzt, der Inhalt von value ist 34.

Neben WriteLine sind in der Klasse Debug noch weitere Methoden zur Ausgabe von Informationen definiert. Tabelle 9.2 gibt darüber Auskunft.


Tabelle 9.2 Ausgabemethoden der Klasse »Debug«

Methode Beschreibung

Write

Schreibt Debug-Informationen ohne Zeilenumbruch.

WriteLine

Schreibt Debug-Informationen mit Zeilenumbruch.

WriteIf

Schreibt Debug-Informationen ohne Zeilenumbruch, wenn eine bestimmte Bedingung erfüllt ist.

WriteLineIf

Schreibt Debug-Informationen mit Zeilenumbruch, wenn eine bestimmte Bedingung erfüllt ist.


Die beiden zuletzt aufgeführten Methoden WriteIf und WriteLineIf schreiben nur dann Debug-Informationen, wenn eine vordefinierte Randbedingung erfüllt ist. Damit lässt sich der Programmcode übersichtlicher gestalten. Beide Methoden sind genauso überladen wie Write bzw. WriteLine, erwarten jedoch im ersten Parameter zusätzlich einen booleschen Wert, zum Beispiel:


public static void WriteIf(bool condition, string message);

Verdeutlichen wir uns den Einsatz an einem Beispiel. Um den Inhalt des Feldes value zu testen, könnten wir in herkömmlicher Weise Folgendes schreiben:


if (value == 77)
  Debug.WriteLine("Inhalt von value ist 77");

Mit WriteLineIf wird daraus eine Codezeile:


Debug.WriteLineIf(value == 77, "Inhalt von value ist 77");

Wie Sie später noch sehen, bieten uns die Methoden WriteLineIf und WriteIf sehr bequem einzusetzende Möglichkeiten, eine Ablaufprotokollierung zu steuern.

Einrücken der Ausgabeinformation

Die Klasse Debug stellt uns Eigenschaften und Methoden zur Verfügung, um die Debug-Ausgaben einzurücken. Mit der Methode Indent wird die Einzugsebene um eins erhöht, mit Unindent um eins verringert. Standardmäßig beschreibt eine Einzugsebene vier Leerzeichen. Mit der Eigenschaft IndentSize kann ein anderer Wert bestimmt werden. IndentLevel erlaubt, eine bestimmte Einzugsebene festzulegen, ohne Indent mehrfach aufrufen zu müssen.

An einem Beispiel wollen wir uns noch die Auswirkungen ansehen.


Debug.WriteLine("Ausgabe 1");
Debug.Indent();
Debug.WriteLine("Ausgabe 2");
Debug.IndentLevel = 3;
Debug.WriteLine("Ausgabe 3");
Debug.Unindent();
Debug.WriteLine("Ausgabe 4");
Debug.IndentSize = 2;
Debug.IndentLevel = 1;
Debug.WriteLine("Ausgabe 5");

Der Code führt zu folgender Ausgabe:


Ausgabe 1
    Ausgabe 2
            Ausgabe 3
        Ausgabe 4
  Ausgabe 5

Die Methode »Assert«

Mit der Methode Assert können Sie eine Annahme prüfen, um beispielsweise unzulässige Zustände festzustellen. Die Methode zeigt eine Fehlermeldung an, wenn ein Ausdruck mit false ausgewertet wird.


Debug.Assert(value >= 0, "value ist negativ");

Hat die Eigenschaft value einen Wert, der kleiner 0 ist, erscheint auf dem Bildschirm die in Abbildung 9.3 gezeigte Nachricht.

Abbildung 9.3 Die Meldung der Methode »Debug.Assert«

Das Dialogfenster enthält neben der dem zweiten Parameter übergebenen Zeichenfolge auch Informationen darüber, in welcher Klasse und welcher Methode der Assertionsfehler aufgetreten ist.


Galileo Computing - Zum Seitenanfang

9.2.3 Die Klasse »Trace« Zur nächsten ÜberschriftZur vorigen Überschrift

Die Klasse Trace unterscheidet sich in der Liste ihrer Eigenschaften und Methoden nicht von Debug. Dennoch gibt es einen Unterschied, der sich nur bei einem Wechsel der Build-Konfiguration zwischen Release und Debug bemerkbar macht (siehe Abbildung 9.4).

Abbildung 9.4 Die Einstellung der Debug/Release-Build-Konfiguration

Die Debug/Release-Konfiguration

Standardmäßig ist bei jedem Projekt die Konfiguration Debug ausgewählt. Anweisungen, die auf den Klassen Debug oder Trace basieren, werden dann grundsätzlich immer bearbeitet. Wird jedoch die Konfiguration Release gewählt, ignoriert der C#-Compiler Aufrufe auf der Klasse Debug, während Aufrufe auf Trace weiterhin bearbeitet werden.

Das ist aber noch nicht das Wesentlichste. Viel wichtiger ist die Tatsache, dass Aufrufe auf Trace kompiliert werden – unabhängig davon, ob Sie die Konfiguration Debug oder Release eingestellt haben. Viele Trace-Anweisungen vergrößern deshalb auch das DLL- bzw. EXE-Kompilat. Andererseits hat der Entwickler hier auch eine einfache Möglichkeit, bestimmte Zustände zu protokollieren, die sich zur Laufzeit einstellen und geprüft werden müssen.

Unterhalb des Verzeichnisses, in dem sich die Quellcodedateien befinden, legt die Entwicklungsumgebung das Verzeichnis \bin an, dem selbst je nach eingestellter Build-Konfiguration die beiden Verzeichnisse \Debug und \Release untergeordnet sind. Abhängig von der Konfigurationseinstellung wird das Kompilat der ausführbaren Datei in eines dieser beiden Unterverzeichnisse gespeichert.

Debug-Informationen, die beim Kompilieren generiert werden, sind in einer Datei mit der Dateierweiterung .PDB im Verzeichnis gespeichert. Der Debugger nutzt die darin enthaltenen Informationen, um Variablennamen und andere Informationen während des Debuggens in einem sinnvollen Format anzuzeigen.


Galileo Computing - Zum Seitenanfang

9.2.4 Ablaufverfolgung mit »TraceListener«-Objekten Zur nächsten ÜberschriftZur vorigen Überschrift

Standardmäßig erfolgt die Ausgabe der Ablaufverfolgungsmeldungen im Fenster Ausgabe. Mit dem Schließen des Ausgabe-Fensters gehen auch die Meldungen verloren. Das .NET Framework stellt Ihnen aber auch Konzepte zur Verfügung, um die Meldungen beispielsweise in eine Datei oder das Ereignisprotokoll von Windows umzuleiten. Verantwortlich dafür sind Listener-Objekte.

Im Namespace System.Diagnostics sind fünf Listener vordefiniert, die alle aus der abstrakten Klasse TraceListener abgeleitet sind:

  • ConsoleTraceListener
  • DefaultTraceListener
  • DelimitedListTraceListener
  • TextWriterTraceListener
  • EventLogTraceListener

Eine Beschreibung der genannten Klassen können Sie Tabelle 9.3 entnehmen.


Tabelle 9.3 Die Listener-Klassen des Namespace »System.Diagnostics«

Listener-Klasse Beschreibung
ConsoleTraceListener

Die ConsoleTraceListener-Klasse schreibt Ablaufverfolgungs- und Debug-Meldungen in die Konsole.

DefaultTraceListener

Die Klasse DefaultTraceListener sendet die Ausgabe an die Entwicklungsumgebung. Das ist auch gleichzeitig die Standardeinstellung.

DelimitedListTraceListener

Leitet die Ablaufverfolgungs- oder Debug-Ausgabe an einen Textwriter, z. B. einen Streamwriter, oder in einen Stream.

EventLogTraceListener

Die Klasse EventLogTraceListener leitet die Ausgabe an das Windows-Ereignisprotokoll weiter.

TextWriterTraceListener

Der Empfänger von TextWriterTraceListener ist ein Stream, beispielsweise um die Informationen in eine Datei zu schreiben.


Listener-Objekte sind an die Klassen Debug und Trace gebunden. Beide Klassen stellen mit Listeners eine Eigenschaft vom Typ TraceListenerCollection bereit. Diese Auflistung weist die bekannten Eigenschaften und Methoden auf, also z. B. Add, Remove, Clear. Beachten Sie, dass die Listener-Objekte sowohl von Trace als auch von Debug gemeinsam benutzt werden. Beide Klassen bedienen sich also derselben Auflistung.

Sehen wir uns nun an einem Beispiel an, wie die Listener eingesetzt werden:


Debug.Listeners.Clear();
ConsoleTraceListener console = new ConsoleTraceListener(Console.Out);
Debug.Listeners.Add(console);
Debug.WriteLine("Debug");
Trace.WriteLine("Trace");

In der ersten Anweisung wird die Listeners-Auflistung mit Clear geleert. Weil damit der standardmäßige Eintrag des DefaultTraceListeners-Objekts gelöscht wird, würde ein Debug.Write- oder Trace.Write-Methodenaufruf keinen Abnehmer mehr finden und im Nirwana verpuffen. In der folgenden Codezeile wird daher die Klasse ConsoleTraceListener instanziiert. Dieses Objekt leitet die Ausgabemeldungen an die Konsole um und wird mit der darauf folgenden Anweisung der TraceListenerCollection übergeben.

Da sich die Klassen Trace und Debug die installierten Listener gleichberechtigt teilen, werden in der Build-Konfiguration Debug beide Meldungen an der Konsole angezeigt.

Die Klasse »TextWriterTraceListener«

Eine Liste von insgesamt sieben Konstruktoren zeugt davon, dass die Klasse TextWriterTraceListener die flexibelste der Listener ist. Neben dem parameterlosen Konstruktor kann dieser Listener auch mit einem TextWriter- oder einem Stream-Objekt verbunden werden. Weitergehende Informationen zu den Writer- und Stream-Klassen erhalten Sie in Kapitel 12, »Arbeiten mit Dateien und Streams«.

Nehmen wir an, dass Sie Ihre Debug-Informationen in einer Datei speichern möchten. Im einfachsten Fall brauchen Sie dem Konstruktor nur eine Zeichenfolge zu übergeben, die den Pfad zu der Datei beschreibt. Anschließend muss der neue Listener registriert werden.


TextWriterTraceListener listener = 
     new TextWriterTraceListener(@"C:\DebugProtocol.txt");
Trace.Listeners.Add(listener);
Debug.WriteLine("Debug.WriteLine-Anweisung");
Trace.WriteLine("Trace.WriteLine-Anweisung");
listener.Flush();
listener.Close();

TextWriterTraceListener schreibt alle Ausgabeinformationen zunächst in einen Puffer. Mit dem Aufruf der Methode Flush wird der Puffer geleert und der Inhalt in die Datei geschrieben. Alternativ dazu können Sie auch die Eigenschaft AutoFlush=true setzen. Mit Close wird das TextWriterTraceListener-Objekt geschlossen. Soll danach die Ablaufverfolgung wieder aufgenommen werden, muss der Listener neu initialisiert und registriert werden.

Die Klassen Debug und Trace bedienen sich derselben Listener. Daher werden beide Write-Anweisungen in der Datei einen Abnehmer finden – vorausgesetzt natürlich, dass die Anwendung im Debug- und nicht im Release-Modus ausgeführt wird.

Ist die Datei bereits vorhanden, werden die Debug-Informationen an den alten Dateiinhalt angehängt. Vielleicht wünschen Sie aber, dass die alten Einträge überschrieben werden. Hier hilft die Überladung des TextWriterTraceListener-Konstruktors weiter, die die Referenz auf ein Stream-Objekt erwartet.

Im folgenden Beispiel wird dem Konstruktor eine Referenz vom Typ FileStream übergeben. Das FileStream-Objekt leitet die Ausgabe in eine Datei um. Ist die Datei bereits vorhanden, wird die alte überschrieben, ansonsten wird eine neue erzeugt. Dafür sorgt die Angabe FileMode.Create.


// --------------------------------------------------------
// Beispiel: ...\Kapitel 9\TextWriterTraceListenerSample 
// --------------------------------------------------------
class Program {
  static void Main(string[] args) {
    Debug.Listeners.Clear();
    FileStream fs = new FileStream(@"C:\ErrorProtocol.txt", FileMode.Create);
    TextWriterTraceListener listener = new TextWriterTraceListener(fs);
    Debug.Listeners.Add(listener);
    try {
      Operation();
    }
    catch(Exception e) {
      string strError = DateTime.Now.ToString();
      Trace.WriteLine("Fehler: " + strError + " - " + e.Message);
    }
    finally {
      listener.Flush();
      listener.Close();}
    }   
  static void Operation() {
    throw new Exception("Unbekannter Fehler");
  }
}

Sehr häufig werden Listener dazu benutzt, aufgetretene Laufzeitfehler im catch-Zweig der Fehlerbehandlung zu protokollieren. In diesem Beispiel wird in der Methode Operation eine Exception mit einer spezifischen Fehlermeldung erzeugt. Neben der Fehlermeldung werden in der Protokolldatei auch noch das Datum und die Uhrzeit des Auftretens der Ausnahme festgehalten. Die entsprechende Information liefert uns die statische Methode Now der Klasse DateTime.

Mehrere Listener verwalten

Ein TextWriterTraceListener beschreibt immer genau einen Informationsabnehmer und eignet sich nicht nur dazu, Fehler zu protokollieren. Sie können einen Listener auch genauso gut dazu benutzen, allgemeine Informationen in einen Stream zu schreiben.

Es sind deshalb auch Szenarien vorstellbar, in denen mehrere Listener mit unterschiedlichen Aufgaben benötigt werden. Grundsätzlich werden Meldungen von jedem registrierten Listener gepuffert. Soll eine Meldung nur von einem bestimmten Listener verarbeitet werden, muss die Registrierung der anderen Listener zumindest zeitweise aufgehoben werden. Mit den Methoden Add, Remove sowie dem Indexer ist das grundsätzlich möglich. Allerdings ist es einfacher, ein Listenelement über einen Namen als über den Index in der Auflistung anzusprechen. Daher stellt die Klasse TextWriterTraceListener Konstruktoren mit einem zweiten Parameter bereit, um den Listener zu benennen. Der Name des Listeners wird in der Eigenschaft Name eingetragen.

Im folgenden Beispiel werden zwei Listener registriert, und beiden wird eine Zeichenfolge übergeben. Anschließend wird ein Listener aus der TraceListenerCollection gelöscht. Die anschließende Meldung wird nur vom verbleibenden Listener weitergeleitet.


// ersten Listener registrieren
TextWriterTraceListener listenerA = 
   new TextWriterTraceListener(@"C:\A.txt", "ListenerA");
Trace.Listeners.Add(listenerA);
// zweiten Listener registrieren
TextWriterTraceListener listenerB = 
   new TextWriterTraceListener(@"C:\B.txt", "ListenerB");
Trace.Listeners.Add(listenerB);
Trace.WriteLine("Erste Information");
// in beide Listener schreiben
listenerA.Flush();
listenerB.Flush();
// listenerB deregistrieren
Trace.Listeners.Remove("ListenerB");
Trace.WriteLine("Zweite Information");
listenerA.Flush();

Sollten Sie versuchen, den Informationsfluss nur über den Aufruf der Flush-Methode auf die einzelnen Listener zu steuern, werden Sie damit wenig Erfolg haben. Denn solange ein Listener registriert ist, werden alle eingehenden Informationen in seinen Puffer geschrieben und beim nächsten Aufruf von Flush an den Abnehmer weitergeleitet.

Die Klasse »EventLogTraceListener«

Alternativ zum TextWriterTraceListener können Sie mit einer Instanz der Klasse EventLogTraceListener Meldungen in das Windows-Ereignisprotokoll schreiben. Auch dieser Listener muss in der Listeners-Auflistung mit der Add-Methode registriert werden. Das Verhalten hinsichtlich der Pufferung der Meldungen ist identisch mit dem, das wir im letzten Abschnitt erörtert haben.

Die Klasse EventLogTraceListener stellt drei Konstruktoren bereit:


public EventLogTraceListener();
public EventLogTraceListener(string);
public EventLogTraceListener(EventLog);

Wenn Sie den parameterlosen Konstruktor aufrufen, müssen alle Eigenschaften konfiguriert werden, bevor Nachrichten an ein Ereignisprotokoll gesendet werden können. Geeigneter erscheint daher der Konstruktor, der eine Zeichenfolge erwartet, die das Ereignisprotokoll namentlich beschreibt. Diagnosemeldungen, die von den Klassen Debug oder Trace geschrieben werden, werden Sie anschließend im Anwendungsprotokoll von Windows wiederfinden.


EventLogTraceListener listener = new EventLogTraceListener("TestLog");
Debug.Listeners.Add(listener);
Debug.WriteLine("Ein Eintrag im Ereignisprotokoll");
listener.Flush();

Mit der dritten Überladung besteht die Möglichkeit, eine Ablaufverfolgungs- oder Debug-Meldung an ein beliebiges Ereignisprotokoll auf der lokalen oder einer anderen Maschine weiterzuleiten. Darüber hinaus können Sie auch ein benutzerdefiniertes Ereignisprotokoll erstellen. Der Konstruktor erwartet dann die Referenz auf ein EventLog-Objekt, mit dem ein Ereignisprotokoll von Windows beschrieben wird.

Im folgenden Codefragment wird ein Ereignisprotokoll namens MyProtocol auf der lokalen Maschine eingerichtet. Die lokale Maschine wird durch eine Zeichenfolge beschrieben, die nur einen Punkt enthält. Sie könnten aber auch den Namen eines entfernten Rechners angeben.


EventLog log = new EventLog("MyProtocol", ".");
log.Source = "MyApplication";
EventLogTraceListener listener = new EventLogTraceListener(log);
Debug.Listeners.Add(listener);
Debug.WriteLine("Ein Eintrag im Ereignisprotokoll");
listener.Flush();

Mit der Eigenschaft Source geben Sie einen Bezeichner für die Ereignisquelle an. Innerhalb der Ereignisprotokolle von Windows muss dieser eindeutig sein.

Wollen Sie in Erfahrung bringen, welche Ereignisprotokolle auf der lokalen Maschine eingerichtet sind, können Sie sich über den Aufruf der statischen Methode GetEventLogs der Klasse EventLog ein Array gleichen Typs besorgen und auf jedem Element die Log-Eigenschaft auswerten, die den Namen des Ereignisprotokolls als Zeichenfolge liefert.


EventLog[] myEventLogs = EventLog.GetEventLogs(".");
foreach(EventLog log in myEventLogs) {
  Console.WriteLine("Protokollname: " + log.Log);
}

Sind keine benutzerdefinierten Ereignisprotokolle in der Registrierungsdatenbank eingetragen, sollte die Ausgabe Application, System und Security lauten.

Grundsätzlich sollten Sie sich darüber im Klaren sein, dass das Ereignisprotokoll nicht dazu dient, eine große Anzahl allgemeiner Meldungen vorzuhalten. Beschränken Sie daher den Einsatz des EventLogTraceListeners auf wirklich wichtige Meldungen.


Galileo Computing - Zum Seitenanfang

9.2.5 Steuerung der Protokollierung mit Schaltern Zur nächsten ÜberschriftZur vorigen Überschrift

Ausgaben, die mit den Write-Methoden der Klassen Debug und Trace erzeugt werden, suchen sich grundsätzlich immer ihre Abnehmer in den registrierten Listenern. Das .NET Framework stellt Ihnen darüber hinaus mit zwei Schalterklassen einen weiteren effektiven Mechanismus zur Verfügung, mit dem Ablaufverfolgungs- und Debug-Ausgaben dynamisch gesteuert werden können. Bei den Schalterklassen handelt es sich um BooleanSwitch und TraceSwitch, die beide in Switch eine gemeinsame Basisklasse haben.

Die Klasse »BooleanSwitch«

Die einfachere Schalterklasse ist BooleanSwitch, die einem An-/Ausschalter ähnelt. Mit einer Instanz dieser Klasse lassen sich alle Ablaufverfolgungs- und Debug-Ausgaben gemeinsam aktivieren oder deaktivieren.

Die Klasse hat nur einen Konstruktor, dem Sie den Namen des Schalters und eine Beschreibung übergeben müssen:


BooleanSwitch mySwitch = new BooleanSwitch("MeinSchalter",
                         "Ablaufverfolgung in MyApplication");

Standardmäßig sind alle Ausgaben zunächst deaktiviert. Sollen die Ausgaben an die Listener weitergeleitet werden, müssen Sie die Eigenschaft Enabled des BooleanSwitch-Objekts auf true setzen:


mySwitch.Enabled = true;

Um die aktuelle Schalterstellung auszuwerten und den weiteren Protokollierungsablauf zu steuern, bieten sich die beiden Methoden WriteIf und WriteLineIf der Klassen Debug und Trace an, z. B.:


Trace.WriteLineIf(mySwitch.Enabled, 
            "Ablaufverfolgung:" + DateTime.Now.ToString());

Nur dann, wenn der Schalter mit Enabled=true aktiviert ist, wird die Meldung mit genauer Datums- und Zeitangabe an alle registrierten Listener geschickt.

Schalterstellung in einer Konfigurationsdatei festlegen

Damit sind aber noch nicht alle Möglichkeiten erschöpft. .NET-Anwendungen können um eine optionale Anwendungskonfigurationsdatei erweitert werden, in der nach Bedarf Einstellungen verändert werden können, ohne dass die Anwendung neu kompiliert werden muss.


Anmerkung

Anwendungskonfigurationsdateien sind bisher noch nicht erwähnt worden. Mehr darüber erfahren Sie in Kapitel 15, »Projektmanagement und Visual Studio 2010«.


Anstatt ein BooleanSwitch-Objekt mit einer Anweisung im Code zu aktivieren bzw. zu deaktivieren, lässt sich das Verhalten des Schalters in der Anwendungskonfigurationsdatei festlegen. Dazu müssen Sie diese folgendermaßen ergänzen:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
   <system.diagnostics>
      <switches>
         <add name="MeinSchalter" value="1" />
      </switches>
   </system.diagnostics>
</configuration>

Innerhalb des switches-Knotens wird mit dem add-Element der entsprechende Schalter festgelegt. Eine Konfigurationsdatei ist nicht nur auf die Steuerung eines Schalters beschränkt. Wollen Sie die Ausgabe über mehrere Schalter steuern, fügen Sie einfach für jeden Schalter ein weiteres add-Element hinzu. Das Attribut name gibt den Namen des Schalters an, das Attribut value die Schalterstellung. Dabei steht 1 für die Aktivierung und 0 für die Deaktivierung des Schalters.

In unserem Beispiel ist der Schalter aktiviert. Deshalb wird die Anwendung auch alle Ausgabemeldungen an die registrierten Listener weiterleiten. Wollen wir die Ausgabemeldungen nicht mehr protokollieren, brauchen wir die Konfigurationsdatei nur mit dem Editor zu öffnen und value auf den Wert 0 zu setzen. Beim nächsten Start der Anwendung ist damit die Protokollierung abgeschaltet. Beachten Sie, dass die Angabe der Schalterstellung im Programmcode dazu führt, dass die Vorgaben in der Konfigurationsdatei verworfen werden.

Die Klasse »TraceSwitch«

Im Gegensatz zu einem BooleanSwitch-Objekt, das nur die Einstellungen »aktiviert« und »deaktiviert« zulässt, können die Ablaufverfolgungs- und Debug-Ausgaben mit einem TraceSwitch-Objekt über die Festlegung mehrerer Ebenen gesteuert werden. Verantwortlich für die Steuerung der Ablaufverfolgung ist die Eigenschaft Level, die vom Typ der Enumeration TraceLevel ist.


public TraceLevel Level {get; set;}

In TraceLevel werden Ablaufverfolgungsebenen definiert, die Sie Tabelle 9.4 entnehmen können.


Tabelle 9.4 Die Konstanten der Enumeration »TraceLevel«

Member Wert Beschreibung
Off

0

Es werden keine Ausgaben an die Listener weitergegeben.

Error

1

Es werden Ausgaben von Fehlermeldungen an die Listener weitergeleitet.

Warning

2

Es werden Ausgaben von Fehler- und Warnmeldungen an die Listener weitergeleitet.

Info

3

Es werden Ausgaben von Fehler-, Warn- und Informationsmeldungen an die Listener weitergeleitet.

Verbose

4

Es werden alle Meldungen an die Listener weitergeleitet.


Wollen Sie ein TraceSwitch-Objekt erzeugen, müssen Sie dem Konstruktor den Namen des Schalters und eine Beschreibung übergeben. Damit gleicht die Instanziierung der von BooleanSwitch. Um die Ablaufverfolgungsebene festzulegen, weisen Sie der Eigenschaft Level einen Wert zu, zum Beispiel:


TraceSwitch switch = new TraceSwitch("MeinSchalter",
                       "Ablaufverfolgung in MyApplication");
switch.Level = TraceLevel.Warning;

Damit in Abhängigkeit von der Ablaufverfolgungsebene mit den Methoden WriteIf und WriteLineIf ein boolescher Wert ausgewertet werden kann, sind in der Klasse TraceSwitch die Eigenschaften

  • TraceError
  • TraceWarning
  • TraceInfo
  • TraceVerbose

definiert, die je nach Schalterstellung true oder false zurückgeben. Um die Ausgabe der Meldungen zu steuern, müssen Sie in den Methoden WriteIf oder WriteLineIf nur eine der Eigenschaften überprüfen:


Trace.WriteLineIf(switch.TraceWarning,
            "Ablaufverfolgung:" + DateTime.Now.ToString());

Die Einstellung der Eigenschaft Level umfasst gleichzeitig auch die niedriger bewerteten Einstellungen, mit Ausnahme von TraceLevel.Off. Wenn im Code beispielsweise die Ablaufverfolgungsebene auf TraceLevel.Info festgelegt ist, bedeutet dies, dass die Eigenschaften TraceError, TraceWarning und TraceInfo true liefern, während TraceVerbose false ist.

Zur Steuerung des oder der TraceSwitch-Objekte bietet sich ebenfalls die Anwendungskonfigurationsdatei an. Dem Attribut value müssen Sie für eine bestimmte Ablaufverfolgungsebene einen zugeordneten Wert übergeben. Sie können diesen Tabelle 9.4 entnehmen. Die folgende Konfigurationsdatei beschreibt die Einstellung TraceLevel.Info.


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
   <system.diagnostics>
      <switches>
         <add name="MeinSchalter" value="3" />
      </switches>
   </system.diagnostics>
</configuration>


Galileo Computing - Zum Seitenanfang

9.2.6 Bedingte Kompilierung topZur vorigen Überschrift

Die bedingte Kompilierung ermöglicht es, Codeabschnitte oder Methoden nur dann zu kompilieren, wenn ein bestimmtes Symbol definiert ist. Üblicherweise werden bedingte Codeabschnitte dazu benutzt, während der Entwicklungsphase den Zustand der Anwendung zur Laufzeit zu testen. Bevor ein Release-Build der Anwendung erstellt wird, wird das Symbol entfernt. Die Abschnitte, deren Code als bedingt kompilierbar gekennzeichnet ist, werden dann nicht kompiliert.

Der folgende Code zeigt ein Beispiel für bedingte Kompilierung:


#define MYDEBUG
using System;
class Program {
  static void Main(string[] args) {
    #if(MYDEBUG)
      Console.WriteLine("In der #if-Anweisung");
    #elif(TEST)
      Console.WriteLine("In der #elif-Anweisung");
    #endif
  }
}

Mit der Präprozessordirektive #define wird das Symbol MYDEBUG definiert. Symbole werden immer vor der ersten Anweisung festgelegt, die selbst keine #define-Präprozessordirektive ist. Werte können den Symbolen nicht zugewiesen werden. Die Präprozessordirektive gilt nur in der Quellcodedatei, in der sie definiert ist, und wird nicht mit einem Semikolon abgeschlossen.

Mit #if oder #elif wird das Vorhandensein des angegebenen Symbols getestet. Ist das Symbol definiert, liefert die Prüfung das Ergebnis true, und der Code wird ausgeführt. #elif ist die Kurzschreibweise für die beiden Anweisungen #else und #if. Da im Beispielcode kein Symbol namens TEST definiert ist, wird die Ausgabe wie folgt lauten:


In der #if-Anweisung

Standardmäßig sind in C#-Projekten die beiden Symbole DEBUG und TRACE vordefiniert. Diese Vorgabe ist im Projekteigenschaftsfenster eingetragen (siehe Abbildung 9.5) und hat anwendungsweite Gültigkeit. Das Projekteigenschaftsfenster öffnen Sie, indem Sie im Projektmappen-Explorer den Knoten Properties doppelt anklicken. Sie können die Symbole löschen oder auch weitere hinzufügen, die ihrerseits alle durch ein Semikolon voneinander getrennt werden müssen.

Abbildung 9.5 Festlegung der Symbole im Projekteigenschaftsfenster

Das Projekteigenschaftsfenster bietet darüber hinaus den Vorteil, dass sich die Symbole einer bestimmten Build-Konfiguration zuordnen lassen. Wählen Sie in der Dropdown-Liste Konfiguration die Build-Konfiguration aus, für die die unter Bedingte Kompilierungskonstanten angegebenen Symbole gültig sein sollen. Wenn Sie beispielsweise keine #define-Präprozessordirektive im Code angeben, dafür aber der Debug-Konfiguration das Symbol DEBUG zugeordnet haben, wird der in #if(DEBUG) bis #endif eingeschlossene Code im Debug-Build mitkompiliert, im Release-Build jedoch nicht.

Die im Projekteigenschaftsfenster definierten Konstanten gelten projektweit. Um in einer einzelnen Codedatei die Wirkung eines Symbols aufzuheben, müssen Sie das Symbol hinter der #undef-Direktive angeben.

Bedingte Kompilierung mit dem Attribut »Conditional«

Häufig ist es wünschenswert, eine komplette Methode als bedingt zu kompilierende Methode zu kennzeichnen. Hier hilft Ihnen .NET mit dem Attribut Conditional aus dem Namespace System.Diagnostics weiter.

Damit eine komplette Methode als bedingt kompilierbar gekennzeichnet wird, muss das Conditional-Attribut (wie im folgenden Beispiel gezeigt) vor dem Methodenkopf in eckigen Klammern angegeben werden. In den runden Klammern wird das Symbol als Zeichenfolge genannt:


[Conditional("DEBUG")]
public void ConditionalTest() {   
  ...
}

Die Methode ConditionalTest wird jetzt nur dann kompiliert, wenn das Symbol DEBUG gesetzt ist. Sie können auch mehrere Attribute mit unterschiedlichen Symbolen angeben. Kann eines der Symbole ausgewertet werden, wird die Methode ausgeführt. Anders als bedingter Code, der durch #if - #endif eingeschlossen ist, wird eine Methode, der das Conditional-Attribut angeheftet ist, immer kompiliert.

Sie müssen beachten, dass eine Methode mit einem Conditional-Attribut immer den Rückgabetyp void haben muss und nicht mit dem Modifizierer override gekennzeichnet sein darf.



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# 2010

Visual C# 2010
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 2010
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.


Nutzungsbestimmungen | Datenschutz | Impressum

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

Cookie-Einstellungen ändern