Inhaltsverzeichnis         

5 Allgemeine Klassen des .NET Frameworks

5.1 Debugging und Ablaufverfolgung

Zum Testen von Software sowie zur Laufzeitanalyse liefert .NET Tools in der Klassenbibliothek unter System.Diagnostics.

Tools

Kurzbeschreibung

Debuggerattribute

Dienen der detailierten Steuerung des Debug- Prozesses. So können bereits ausgetestete Methoden für den Einzelschrittmodus gesperrt oder spezielle Member können im Überwachungsfenster in der Wertespalte als Liste ausgegeben werden.

Debug- Klasse

Die Klasse ist nur im Debug- Build aktiv. Im Releasebuild wird die Methodenaufrufe der Klasse nicht in MSIL compiliert.

Die Klasse stellt eine Schnittstelle zum Debuger da. Der Debugger kann über Debug gestartet, Breakpoints können ausgelöst und Meldungen an den Debugger übergeben werden. Desweiteren können Behauptungen über den aktuellen Ausführungszustand (Assert) aufgestellt und mit dem Ist- Zustand verglichen werden.

Trace- Klasse

Ausgabe von Meldungen im Debug, als auch im Release- Mode.

Trace- Listener

Sind Ausgabemedien für die von der Klasse Trace oder Debug generierten Meldungen

TraceSource


TraceSwitch, BooleanSwitch

Sind Schalter, die über Konfigurationsdateien eingestellt, und in Bedingten Debug- oder Trace- Methoden ausgewertet werden können.

5.1.1 Aktivieren von Debug oder Trace

Die Instrumentierung einer Anwendung mit Debug oder Trace- Klasse wird über Compilerschalter gesteuert. Diese werden durch Compileroptionen oder Präprozessorkonstanten gesetzt:

Aktiviern von

C# Compileroption

Präprozessorkonstante

Debug

/d:DEBUG

#define DEBUG

Trace

/d:TRACE

#define TRACE

5.1.2 Phasen der Instrumentierung

Phase

Name

Beschreibung

1

Instrumentierung

Verfolgungscode wird der Anwendung hinzugefügt

2

Ablaufverfolgung

Verfolgungscode schreibt Informationen in ein angegebenes Ziel

3

Analyse

Ablaufverfolgungscode wird ausgewertet

5.1.3 Trace und Debug- Klasse

Trace und Debug- Klasse haben einen fast identischen Aufbau




5.1.4 Switch

Mittels Kompilerschalter kann die Implementierung der Ablaufverfolgung komplett an oder abgeschaltet werden. Ist die Ablaufverfolgung implementiert, kann der Umfang der Protokollierung mittels von der Klasse Switch abgeleiteter Klassen fein gesteuert werden. Die Switche können durch eine Konfigurationsdatei gestellt werden.


Ein Switch wird z.B. wie folgt eingesetzt:

public class BatchProcessor<TWorker> : IBatchProcessing        
        where TWorker : DMS.IWorker
    {
        // Protokollierung 
        private mko.CLog log;
        TraceSwitch ts;
        ...
        public BatchProcessor(mko.CLog log, TWorker worker)
        {
            this.log = log;
            ...
            ts = new TraceSwitch("TraceBatchProcessor", "Diagnoseprotokolle des Batchprocessors");
        }  

        ... 
        void IBatchProcessing.pushJob(Job job)
        {           

            try
            {
                lock (JobQueue) {

                    JobStorage.Add(job.JobId, job);
                    JobQueue.Enqueue(job);

                    job.SetWaiting();

                    Trace.WriteLineIf(ts.TraceInfo, string.Format("Job id= {0:D} in Warteschlange gestellt", job.JobId));  
                    
                    ...
            }
            catch (Exception ex)
            {
                log.LogError(0, "PushJob: " + ex.Message);
                Trace.Fail("PushJob: " + ex.Message);
            }
        }

Konfigurieren kann man den Switch über die Konfigurationsdatei wie folgt:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.diagnostics>    
    <trace autoflush="true">
      <!-- Trace- Klasse konfigurieren -->
      <listeners>
        <!-- Hinzufügen eines Listeners, der in eine Datei protokolliert-->
        <add name="DemoListener"
             type="System.Diagnostics.TextWriterTraceListener"
             initializeData="MyTrace.trc"/>
      </listeners>
    </trace>
    <switches>
      <!-- Protokollieren aller Meldungen aus der Stapelverarbeitung-->
      <add name="TraceBatchProcessor" value="4"/>
    </switches>
  </system.diagnostics>
</configuration>

5.2 Schreiben in Ereignisprotokolle des Betriebssystems mit EventLog

Mit der Ereignisanzeige werden zentrale Protokolle über die Aktivitäten auf dem System geführt. NET stellt mit der Klasse EventLog eine leistungsfähige API für den Zugriff auf diese Protokolle bereit.

5.2.1 Grundsätzliches

Nach der Installation von Windows existieren die drei Systemprotokolle Application, System und Security. Dieser Satz kann erweitert werden. Die Klasse EventLog erzeugt automatisch die neuen Protokolle, wenn dem Konstruktor der Name eines neuen Protokolls übergeben wird. Sollte die Ereignisanzeige bereits geöffnet sein, dann muß diese geschlossen und wieder neu geöffnet werden, um die neuen Protokolldateine sichtbar zu machen.

Um in einem Protokoll zu schreiben, muß zuvor eine Datenquelle registiert werden. Die Datenquelle ist ein beliebiger Name, welcher der Methode CreateEventSource übergeben werden muß. Nachdem eine Datenquelle angemeldet wurde, erfolgen alle mit ihr verknüpften Schreibvorgänge in dem Ereignisprotokoll, für das sie registriert wurde. Um diese Bindung an ein Ereignisprotokoll aufzuheben, muß die Quelle mittels DeleteEventSource gelöscht, und der Computer neu gebootet werden !

5.2.2 EventLog




Beispiel

        static void Main(string[] args)
        {
            try
            {
                // Create the source, if it does not already exist.
                if (!System.Diagnostics.EventLog.SourceExists("Martin3"))
                {
                    System.Diagnostics.EventLog.CreateEventSource("Martin3", "Application");
                    Console.WriteLine("CreatingEventSource");
                }

                System.Diagnostics.EventLog log = new System.Diagnostics.EventLog();
                log.Source = "Martin3";

                log.WriteEntry("Hallo- ein Eintrag");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }



5.3 Senden von Emails

Namsspace: System.Net.Mail

Im .NET Framewaork gibt es zwei Implementierungen für Emailsverkehr: System.Web.Mail (.NET 1.1, veraltet) und System.Net.Mail (.Net 2.0, aktuell).

Emails werden als Objekte der Klasse MailMessage dargestellt.


Beispiel:

MailMessage email = new MailMessage();

// Adresse
email.To.Add("viagra@junkmail.com"); 

// Betreff               
email.Subject = "vermülltes Postfach";

// Inhalt
email.Body = "Liebe Spammer, ich habe Euch so lieb …";  

Das Senden erledigt die Klasse SmtpClient.

try
{
  // ...          

  // Im Konstruktor wird IP- Adr. und Port des Smtp- Servers übergeben, der die 
  // Email an das Zielpostfach weiterleiten soll
  SmtpClient client = new SmtpClient("192.168.2.17", 25);
  client.Send(email);
}
catch (Exception ex)
{
   Debug.WriteLine("Beim Senden einer Mail: " + ex.Message);
}

5.4 Applikationsdomänen






5.4.1 AppDomain- Klasse






5.4.2 Erzeugen einer AppDomain und Instanziieren einer Klasse in dieser

Mittels der AppDomain- Klasse kann eine neue Anwendungsdomäne in einem Prozess erzeugt werden. Anschließend kann in dieser ein Objekt aus einer Assembly geladen werden.

static void Main(string[] args) {

 Lib.MyClass obj1 = new Lib.MyClass();
 obj1.PrintDomainName();

 // Erzugen der neuen AppDomain
 AppDomain newAppDomain = AppDomain.CreateDomain("newDomain");

 // Instanziieren eines Objektes in der AppDomain und herstellten der Verbindung über einen Proxy (...Unwrap)
 Lib.MyClass obj2 = (Lib.MyClass)newAppDomain.CreateInstanceAndUnwrap("NameDerAssemblyVonLibMyClass", "Lib.MyClass");

 // Zugriff auf ein Objekt aus einer anderen Assembly (Remoting!)
 obj2.PrintDomainName();

 Console.ReadLine();
}

Das Objekt, welches hier in der neuen Assembly instanziiert wurde, ist über AppDomain- Grenzen hinweg aufrufbar, da es von MarshalByRef Objekt abgeleitet ist.

// Inhalt der Assembly  NameDerAssemblyVonLibMyClass.dll
namespace Lib
{   
    public class MyClass : MarshalByRefObject
    {
        public void PrintDomainName()
        {
            Console.WriteLine("AppDomain.FriendlyName= {0}", AppDomain.CurrentDomain.FriendlyName);
        }
    }
}

5.4.3 Erzeugen einer Appdomain und laden einer Assembly in dieser

static void StarteU1HalloWelt()
        {
            // Create a new application domain.
            AppDomain domain = System.AppDomain.CreateDomain("MyAppDomain");

            // Try to execute the assembly.
            try
            {
                // Laden und ausführen der Assembly u1-Hallo.exe,
                // die sich im Verzeichnis .\Assemblies bedfindet
                domain.ExecuteAssembly(@"Assemblies\u1-Hallo.exe");
            }
            catch (PolicyException e)
            {
                // Hatte die Assembly bei der Ausführung nicht die erfordelichen
                // Rechte, dann wird wird eine PolicyException geworfen
                Console.WriteLine("PolicyException: {0}", e.Message);
            }

            // Applicationsdomäne wird aus dem laufenden Prozess entladen
            AppDomain.Unload(domain);
        }

5.5 Delegates: Funktionspointer in C# und VB.NET

Namespace: System.Delegat, System.MulticastDelegate

Delegates sind typisierte Funktionspointer. Durch sie wird es möglich, Einsprungpunkte von Methoden als Parameter einer Methode zu übergeben oder in einer Liste zu verwalten. Anwendung finden Delegates in Callback- Methoden bzw. zur Implementierung von Ereignissen.

Im .NET Framework sind Delegates nichts weiter als Objekte spezieller Klassen. Da jeder Delegate eine individuelle Parameterliste aufweisen kann, können die Delegates nicht alle Objekte einer gemeinsamen Klasse sein. Der C#/VB.NET- Kompiler generiert für jeden Delegate automatisch eine versigelte Klasse die von der Klasse System.Delegate abgeleitet ist. Diese Klassen haben folgenden Aufbau:




5.5.1 Deklaration

Jeder Delegate muß vor seiner Verwendung deklariert werden:

[Zugriffsmodifikator] delegate Methodendeklaration
z.B.:
// C#: Funktionszeigertyp von Handlern zur Behandlung des Arbeitsfortschritts- Event
public delegate bool DGEventProgress(DirTreeProgressInfo info);        
' VB.NET
public Delegate Sub DGProgress(int count_

Im folgenden Beispiel wird mittels des Delegaten DGProgress für die Methode scanDir der Klasse CDirTree ein Callback implementiert, durch welche der Aufrufer fortlaufend über den Arbeitsfortschritt informiert wird.

class CDirTree {

 public DGProgress CallBackProgress;

 public void scanDir(string path) {
   :

   // Anzeige des Verarbeitungsstandes
   if (icount_files % 100 == 0) 
     if (CallBackProgress != null)
         CallBackProgress(icount_dirs, icount_files);
   :
 }
}

5.5.2 Zuweisen von Methoden an Delegaten (Registrierung)

Die Zuweisung einer Methode geschieht durch einen Konstruktor. Dieser Prozess wird auch als Registrierung bezeichnet:

class Class1 {

         // Callback für Anzeige des Forschrittes
        static void Progress(int anz_dirs, int anz_files) 
        {
           Console.WriteLine("Progress: Dirs = {0}, Files= {1}", anz_dirs, anz_files);
        }


   static void Main() {
         CDirTree dt = new CDirTree();

         // Callbackroutine registrieren: #methode Progress wird dem Delegaten zugewiesen        
        dt.CallBackProgress = new DGProgress(Progress);

        dt.traverse("c:\\");
}
      

5.5.2.1 Registrieren mehrerer Methoden für einen Delegate

Ein Delegate kann auch eine Liste von Funktionspointern speichern. Wird der Delegate aufgerufen, dann werden alle für den Delegate registrierten Methoden nacheinander ausgeführt. Die Registrierung erfolgt in diesem Fall mittels des += Operators:

class Class1 {

         // Callback für Anzeige des Forschrittes
        static void Progress(int anz_dirs, int anz_files) 
        {
           Console.WriteLine("Progress: Dirs = {0}, Files= {1}", anz_dirs, anz_files);
        }

        // Callback für Anzeige des Forschrittes
        static void Progress2(int anz_dirs, int anz_files) 
        {
           Console.WriteLine("Progress2: Dirs = {0}, Files= {1}", anz_dirs, anz_files);
        }


   static void Main() {
         CDirTree dt = new CDirTree();

         // Zwei Callbackroutine registrieren:
        dt.CallBackProgress += new DGProgress(Progress);
        dt.CallBackProgress += new DGProgress(Progress2);

        dt.traverse("c:\\");
}
     

5.5.3 Asynchroner Methodenstart

Delegates erweitern die Möglichkeiten beim Aufruf von Methoden um Asynchrone Methodenaufrufe. Dazu werden neben der Synchronen Methode Invoke zum Aufruf einer Prozedur/Funktion auch asynchrone Methoden wie BeginInvoke und EndInvoke angeboten.




5.6 Collections

Collection werden im .NET durch Klassen gebildet, die Schnittstellen aus der folgenden Hierarchie implementieren:


Im Namensraum System.Collections werden zur allgemeinen Verwendung folgende Klassen bereitgestellt

ArrayList

Queue

Stack

Hashtable

implementiert IList

implementier ICollection

implementiert ICollection

implementiert IDictionary

Stellt Funktionalität eines gewöhnlichen Arrays dar, mit dem vorteil, daß Löschen von Elementen zu keinen Lücken führt.

Realisiert einen FIFO- Speicher

Realisiert einen Stapelspeicher

realisert effiziente Liste mit zugriff über nichtnummerische Indizes (Schlüssel)

ArrayList re_positionen = new ArrayList();

 

 

 

5.6.1 IEnumerable und ICollection




5.6.2 IList




5.6.3 IDictionary

Beschreibt eine Schnittstelle von Collections, in denen Werte unter bestimmten Schlüsseln abgelegt werden:




5.6.4 Sortieren von generischen Listen




5.7 Iteratoren (NET 2.0)

In vielen .Net Sprachen ist der foreach - Algorithmus ein Merkmal der Sprache. Alle Klassen, auf deren Objekte der foreach- Algo. anwendbar sein soll, müssen dabei die IEnumerable- Schnittstelle unterstützen, welche wiederum ein Objekt mit der IEnumerator- Schnittstelle liefert. Mittels der IEnumerator- Methoden diese Objektes kann dann der foreach- Algo. die Menge durchlaufen. Folgendes Objektsequenzdiagramm stellt den Ablauf dar:


Der Aufwand zur Implementierung der IEnumerator- Schnittstelle in einer Klasse kann in .NET 2.0 eingespart werden durch Iteratoren.

Definition

Iterator

Ein Iterator ist eine Liste, welche für jeden Schleifendurchlauf des foreach- Algorithmus den einzusetzenden Wert aus der Menge, durch die iteriert wird, definiert.

Aus dieser Liste generiert der C#- Compiler Klassendeklarationen, die gemäß der Beschreibung die IEnumerator- Schnittstelle implementieren.




Die Werte, die ein Iterator bei einer Iteration liefert, werden durch eine Folge von yield retun <Ausdruck> - definiert. Wiederholen sich die yield- return Anweisungen nach bestimmten Gesetzmäßigkeiten, so kann dies auch in einer for - Schleifenkonstruktion ausgedrückt werden. Zu beachten ist hierbei, das die for- Schleifenkonstruktion hier die Definition der Liste aus yield Anweisungen darstellt, deren Ergebnisse für jeden foreach- Schleifendurchlauf genutzt werden, und nicht wie gewöhnlich eine Kontrollanweisung zur Steuerung des Programmflusses ist.

Im Beispiel wird eine Klasse mit Iteratordeklarationen implementiert. Wie aus dem Beispiel ersichtlich, kann eine Klasse beliebig viele Iteratordeklarationen enthalten. Iteratoren können auch mit Parametern ausgestattet werden.

    class CMengeMitIteratoren
    {
        int[] primz = { 2, 3, 5, 7, 11, 13, 17, 23 };

        // Iterator, der die ersten 3 Eniräge durchläuft
        public System.Collections.IEnumerable ItErsteDrei()
        {
            yield return primz[0];
            yield return primz[1];
            yield return primz[2];
        }

        // Iterator, welcher alle Einträge durchläuft
        public System.Collections.IEnumerable ItAlle()
        {
            for (int i = 0; i < primz.Length; i++)
                yield return primz[i];
        }

        // Iterator, welcher die ersten n Einträge durchläuft
        public System.Collections.IEnumerable ItErsteN(int n)
        {
            for (int i = 0; i < Math.Min(n, primz.Length); i++)
                yield return primz[i];
        }

    }

    // Anwendung von Iteratoren
    static void Main(string[] args)
    {

       CMengeMitIteratoren Menge = new CMengeMitIteratoren();
       Console.WriteLine("Ausgabe der ersten drei");
       foreach (int z in Menge.ItErsteDrei())
       {
          Console.WriteLine(z);
       }

       Console.WriteLine("Nochmalige Ausgabe der ersten drei");
       foreach (int z in Menge.ItErsteDrei())
       {
          Console.WriteLine(z);
       }

       Console.WriteLine("Ausgabe aller");
       foreach (int z in Menge.ItAlle())
       {
          Console.WriteLine(z);
       }

       Console.WriteLine("Ausgabe der ersten 5");
       foreach (int z in Menge.ItErsteN(5))
       {
          Console.WriteLine(z);
       }

5.8 Klassen für den Zugriff auf Dateien und Verzeichnisse

Namespace: System.IO

5.8.1 Directory

Klasse enthält ausschließlich statische Methoden. Dient dem Zugriff auf Verzeichnisse.




5.8.2 Path

Path enthält ausschließlich statische Member und dient zur Analyse von Dateipfaden:




5.8.3 File

Die Klasse File enthält ausschließlich statische Member. Sie liefert Funktionen zum Öffnen und Anlegen von Dateien.




5.8.4 Hierarchie der Streams




5.8.5 Basiskalasse Stream




5.8.6 FileStream

Die Klasse dient zum byteweisen Lesen und Schreiben von Dateien

5.8.7 MemoryStream

Mittels MemeoryStream kann ein byte[] - Array wie ein Datenstrom behandelt werden. In Kombination mit dem weiter unten behandelten BinaryReader können so Daten serialisert werden. Die Serialisierung in byte[]- Arrays ist besonders für die Übertragung in Netzwerken wichtig.

5.8.8 Reader/Writer

Zum Lesen und schreiben von typisierten Strömen bietet .NET sog. Reader und Writer- Klassen an. TextReader und TextWriter sind abstrakte Basisklassen, von denen Klassen zum Lesen bzw. Schreiben in Datenströmen oder Strings abgeleitet sind. Standard ist das Lesen und Schreiben von UTF8- Datenströmen. Durch übergabe einer Encoding- Klasse aus System.Text kann dieses Verhalten geändert werden.

BinaryReader und BinaryWriter ermöglichen das typisierte Lesen und Schreiben von und in binären Datenströmen.

Achtung: Der Unterschied zw. StreamWriter und BinaryWriter besteht darin, daß StreamWriter eine Unicode- Datei mit einer gültigen BOM (Byte Order Mark)

Im folgenden ist eine Liste gültiger Byte- Order Marken für Unicode- Dateien aufgelistet. Insbesondere XmlReader setzen diese vorraus!

Bytes

Encoding Form

00 00 FE FF

UTF-32, big-endian

FF FE 00 00

UTF-32, little-endian

FE FF

UTF-16, big-endian

FF FE

UTF-16, little-endian

EF BB BF

UTF-8






5.8.9 Reader und Stream.Seek

Achtung: Wenn die Leseposition mittels Seek des Streams geändert wird, der einem Reader zugrunde liegt, kommt es bei den folgenden Leseoperationen über den Reader zu undefinierten Verhalten !

Wenn die Leseposition in einem Stream geändert werden muß, dann ist der darauf operierende Reader nach der Positionierung auf dem Stream zu instanziiren wie folgt:

System.IO.Stream ReportStream = System.IO.File.Open(ReportFilename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
System.IO.StreamReader sr = new System.IO.StreamReader(ReportStream, System.Text.Encoding.ASCII);
// Einlesen des Kopfes
string Tabellenkopf = sr.ReadLine();
...
ReportStream.Seek(BeginByteOffset, SeekOrigin.Begin);
StreamReader sr2 = new StreamReader(ReportStream, System.Text.Encoding.ASCII);    
string line = sr2.ReadLine();              

In diesem Fall wird die Zeile korrekt ab BeginByteOffset gelesen. Wäre die Position im Stream geändert wurden, nachdem der Stream an den Reader gebunden wurde, wäre das Verhalten beim Lesen undefiniert.

5.9 XML

Das .NET Framework enthält eine umfangreiche Bibliothek zum Analysieren, Lesen und Schreiben von XML- strukturierten Daten. Diese Bibliothek orientiert sich dabei an die vom w3c erlassen Standards.

XML- Daten können als Datenstrom mittels der Klassen XmlReader und XmlWriter- oder im Dokumentbaum (DOM) im Arbeitsspeicher mittels der Klasse XmlDocument verarbeitet werden.

Eine Validierung von Xml Daten gegen eine DTD oder gegen ein Schema wird durch die XmlReader- Klasse unterstützt.

Die Transformation von XML- Daten mittels XSL- Stylesheets wird in .NET 20 durch die Klasse System.Xml.Xsl.XslCompiledTransform realisiert.

5.9.1 Zeichenkodierung

Der XML 1.0 Standard schränkt den zulässigen Zeichensatz für Namen von Elementen und Attributen stark ein. Bei der Bildung von Namen sind neben den alphanummerischen Zeichen nur noch _ zugelassen. Werden z.B. Datenstrukturen in C# in XML serialisert, dann muß sichergestellt werden, das die resultierenden Element- und Attributbezeichner immer noch den XML-Einschränkungen genügen. Denn C# erlaubt z.B. auch Umlaute in den Bezeichnern.

Mittels der Klassen XMLConvert können Bezeichner in gültige XML- Bezeichner umgewandelt werden. Die Rückwandlung in das ursprüngliche Format wird ebenfalls unterstützt. Beispiel:

string txt = "<Hallo>+'Welt'@?$!__12:";

string HtmlCodiert = System.Web.HttpUtility.HtmlEncode(txt);
Console.WriteLine("{0} =(Html)= {1}", txt, HtmlCodiert);

string XmlCodiert = System.Xml.XmlConvert.EncodeName(txt);
Console.WriteLine("{0} =(XmlName)= {1}", txt, XmlCodiert);
Console.WriteLine("Dekodiert: {0}", System.Xml.XmlConvert.DecodeName(XmlCodiert));


XmlCodiert = System.Xml.XmlConvert.EncodeLocalName(txt);
Console.WriteLine("{0} =(XmlLocalName)= {1}", txt, XmlCodiert);
Console.WriteLine("Dekodiert: {0}", System.Xml.XmlConvert.DecodeName(XmlCodiert));

XmlCodiert = System.Xml.XmlConvert.EncodeNmToken(txt);
Console.WriteLine("{0} =(XmlToken)= {1}", txt, XmlCodiert);
Console.WriteLine("Dekodiert: {0}", System.Xml.XmlConvert.DecodeName(XmlCodiert));

5.9.2 Parsen mittels XmlReader




5.9.2.1 Ablauf

Im Folgenden ist die Anwendung des XmlReaders gezeigt. Zu Beginn ist mittels der Calssfactory Create für ein XML ein Readerobjekt anzulegen. Über die XmlSettings kann ein Schema angegeben werden, gegen das das XML beim Lesen validiert werden soll. Für den Fall der Ungültigkeit kann in den Settings ein Ereignishandler registriert werden. Der Reader sollte am Ende seiner Nutzung immer mittels Close geschlossen werden, um verwaltete und Systemeignen Resourcen freizugeben.




5.9.3 Schreiben mittels XmlWriter




5.9.3.1 Beispiel

namespace DMS
{
    public class FeatureCollectorXml : DMS.FeatureCollector.IFeatureCollector
    {
        #region IFeatureCollector Member

        XmlWriter writer;

        public void Open(string path)
        {
            XmlWriterSettings sett = new XmlWriterSettings();
            sett.Encoding = System.Text.Encoding.UTF8;
            sett.Indent = true;            

            writer = XmlWriter.Create(path, sett);
        }

        public void Close()
        {
            writer.Close();
        }

        void DMS.FeatureCollector.IFeatureCollector.Collect(ref DMS.FeatureCollector.DmsMinDbDataSet FeatureData)
        {
            // Erzeugen von Xml- Dateien aus den Tabellen des DataSets mittels 
            // der Methoden WriteXml von DataSet und DataTable
            DataSetToXml(FeatureData);

            // Dokument einleiten -> XML Dekleration schreiben
            writer.WriteStartDocument();

            // Wurzelelement
            writer.WriteStartElement("FeatureCollection");



            foreach (DMS.FeatureCollector.DmsMinDbDataSet.FileInfosRow row in FeatureData.FileInfos.Rows)
            {
                writer.WriteStartElement("FileInfo");

                // Attribute schreiben
                writer.WriteAttributeString("sizeInBytes", row.SizeInBytes.ToString());
                writer.WriteAttributeString("ext", row.ext);
                writer.WriteAttributeString("ctime", row.ctime.ToString("s"));

                // Inhalt des Elements schreiben
                writer.WriteString(row.path);

                writer.WriteEndElement();
            }



            // Wurzelelement schliessen
            writer.WriteEndElement();

            writer.WriteEndDocument();
        }

        #endregion
    }
}

5.9.4 Dom

DOM steht für Document Object Model und ist ein vom w3c entwickelte Standardschnittstelle für Strukturierte Dokumente wie XML (Das JavaScript im Internetexplorer implementierte bereites einen Vorläufer des DOM). Im DOM wird das strukturierte Dokument als eine Baumstruktur dargestellt, wobei die Elemente, Attribute, Instruktionen, Kommentare und Textinhalte als Knotenobjekte dargestellt werden. DOM definiert für die verschiedenen Knotentypen Klassen. Für den Zugriff auf die Konten werden vom DOM in den Kontoenklassen spezielle Methoden bereitgestellt.

Im NET wird mittels der Klasse XmlDocument ein DOM nach den Vorgaben des w3c für Xml- Dokumente bereitgestellt. Durch sie wird ein XML im Arbeitsspeicher als Dokumentenbaum repräsentiert. Im Unterschied zum XmlReader erhält man so zum einen Wahlfreien Zugriff auf die Knoten im Dokument, wird aber durch die größe des Arbeitsspeichers limitiert.

Im Folgenden ein Beispiel für die Baumdarstellung eines XML:

<?xml version="1.0" encoding="ISO8859-1" standalone="yes"?>
<!-- Ein Kommentar -->
<Planet name="Jupiter">
  <Durchmesser Einheit="km" Wert="142984"/>
  <Mond name="Io">
    <Durchmesser Einheit="km" Wert="3642"/>    
  </Mond>
  <Mond name="Europa">
    <Durchmesser Einheit="km" Wert="3138"/>    
    Europa- ein Wasserplanet ?
  </Mond>
  <Mond name="Ganymede">
    <Durchmesser Einheit="km" Wert="5262"/>    
  </Mond>
  <Mond name="Callisto">
    <Durchmesser Einheit="km" Wert="4800"/>    
  </Mond>
</Planet>

Die entsprechende Baumdarstellung ist folgende:




5.9.4.1 Erzeugen eines Dokumentenbaumes aus XML und zurückschreiben in XML

Zum Anlegen eines Dokumentenbaumes dient die Klasse XmlDocument. Mittels der Load- Methoden kann ein Dom aus einer Xml- Datei oder String erzeigt werden. Die Eigenschaft DocumentElement ist der Dokumentenstamm, unter welchem sich Xml- Instruktionen, DTD, Kommentare und Wurzelelement befinden. DocumentElement ist damit der Einstieg in den Dokumentenbaum.




5.9.4.2 XmlNode- Basisklasse aller Knotenobjekte

Alle Klassen den DOM sind von der Basisklasse XmlNode abgeleitet. Elemente Attribute oder Kommentare lassen sich damit letztendlich als XmlNodes betrachteten. Der DOM- Dokumentenbaum ist ein Baum aus XmlNodes:


Jeder Dokumentknoten hat die Eigenschaften und Methoden von XmlNode, diese müssen aber nicht in jedem Fall einen sinnvolle Implementierung besitzten. Beispielsweise besitzen Kommentarknoten keine Namen.


Die Eigenschaft NodeType zeigt an, zu welcher speziellen Knotenklasse ein Knotenobjekt gehört. Folgende Werte sind möglich:

NodeType

Knotenklasse

Attribute

Attributknoten

CDATA

CDATA- Abschnitt <!CDATA[[ Hallo <xml> Welt ]]>

Comment

Kommetare <!-- Hallo -->

Document

Stammknoten eines XmlDocument

DocumentFragment

Dokumentfragment

DocumentType

DTD- Abschnitt einer XML- Datei

Element

Elementknoten

Entity

Entity- Deklaration <!ENTITY ...>

Notation


ProcessingInstruction

Anweisungen an den Parser <?xml ...>

Text

Textknoten

5.9.4.3 XmlElement

XmlElement ist eine spezialisierte Klasse für Xml- Elemente. Sie bietet Methoden und Eigenschaften zum komfortabelen Zugriff auf die Attribute eines Elements. Zudem können mittels GetElementByTagName - Methode Teilmengen von Kindknoten bestimmt werden.




5.9.4.4 XmlAttribute




5.9.4.5 XmlText




5.9.5 XPathNavigator

        static void Main(string[] args)
        {
            // Dokumentbaum aufbauen
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.Load(Properties.Settings.Default.xmlDoc);

            // Für den Knotenzugriff über XPath wird ein XPathNavigator- Objekt benötigt.
            // Die Dokumentwurzel ist der Bezugspunkt für weitere Navigationen
            XPathNavigator navi = xmlDoc.CreateNavigator();

            // Werden XML- Vokabulare in Namensräumen eingeschlossen, dann sind die
            // Namensraumtabellen des NAvigators in einem Namespace- Manager zu 
            // registrieren
            XmlNamespaceManager namespaceManager = new XmlNamespaceManager(navi.NameTable);

            // Im Namespacemanager werden die eingewsetzten  Namensräume verzeichnet
            namespaceManager.AddNamespace("astro", "http://www.tracs.de/Planeten.xsd");

            // Auswahl eines Kontens in einem Xml Dokument über einen XPath- Ausdruck.
            // Das Ergebnis ist wieder vom Typ XPath- Navigator. Der selektierte Knoten
            // ist der neue Bezugspunkt für weitere Navigationen.
            string xpath = "//astro:Durchmesser";
            XPathNavigator node = navi.SelectSingleNode(xpath, namespaceManager);
            Console.Write("Gefundener Knoten\n{0}\n", node.OuterXml);

            xpath = "//astro:Durchmesser[@Wert=\"4800\"]";
            node = navi.SelectSingleNode(xpath, namespaceManager);
            Console.Write("Gefundener Knoten\n{0}\n", node.OuterXml);
            Ancestors(node);

            xpath = "//astro:Planet[@name = \"Jupiter\"]/astro:Mond/astro:Durchmesser[@Wert < \"5000\"]";
            XPathNodeIterator NodeSelection = navi.Select(xpath, namespaceManager);
            Console.WriteLine("Von {0} selektierte Knoten:", xpath); 
            foreach (XPathNavigator naviNode in NodeSelection)
            {
                Console.WriteLine(naviNode.OuterXml);
                Console.WriteLine("Elternknoten:");
                naviNode.MoveToParent();
                Console.WriteLine(naviNode.OuterXml);
            }        

        }

        private static void Ancestors(XPathNavigator node)
        {
            // Alle Knoten bis zurück zur Wurzel
            XPathNodeIterator iterator = node.SelectAncestors(XPathNodeType.Element, false);
            Console.WriteLine("Alle Knoten bis zurück zur Wurzel");
            foreach (XPathNavigator parentNode in iterator)
            {
                Console.WriteLine(parentNode.OuterXml);
            }
        }

5.9.6 Xslt

        static void Main(string[] args)
        {
            try
            {
                XslCompiledTransform xsltTrafo = new XslCompiledTransform(true);
                
                // Stylesheet laden
                xsltTrafo.Load(Properties.Settings.Default.XsltTabPlanetenMonde); 
                
                // Staistik zu den bei der Compilation erzeugten temporären Dateien
                Console.WriteLine("Basisverzeichnis für temp. Dateien: {0}", xsltTrafo.TemporaryFiles.BasePath);
                Console.WriteLine("Temporäres Verzeichnis: {0}", xsltTrafo.TemporaryFiles.TempDir);
                Console.WriteLine("Anz. temporärer Dateien: {0:D}", xsltTrafo.TemporaryFiles.Count);
                   
                // Transformation des XML- Dokuments in ein Html Dokument mittels des vorkompilierten Stylesheets             
                xsltTrafo.Transform(Properties.Settings.Default.XmlDoc, "Tabelle-der-Palneten-und-Monde.html");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Fehler: " + ex.Message);
            }
        }

5.10 Attribute

In natürlichen Sprachen werden mittels Attribute Eigenschaften von Substantiven festgelegt wie heißer Tee oder grüne Farbe. Analog werden durch Attribute in C# zusätzliche Informationen zu Klassen, Prozeduren und Variablen hinterlegt. Diese Informationen können dann vom Compiler verarbeitet werden, oder werden als Metadaten in der Assembly gespeichert.

Beispiel:

Definieren von Metadaten ür die Assembly:

[Assembly: AssemblyTitle("GeoInfo")]
[Assembly: AssemblyDescription("Zugriff auf Datenbank")] 
[Assembly: AssemblyCompany("mkoSoft")]
  :
[Assembly: AssemblyVersion("1.0.*")]

5.10.1 Selbstdefinierte Attribute

Attribute sind Klassen, die von System.Attribute abgeleitet sind. Jedes Attribut ist selbst wieder mit dem Basisattribut AttributeUsage ausgezeichnet. Damit ergibt sich folgende Grundstruktur für eine Attributdeklaration:

[AttributeUsage(AttributeTargets, // Elemente, die attributiert werden können
                Inherited,        // Wird das Attribut vererbt
                AllowMultiple)]   // Kann ein Element mehrfach attributiert werden
public Class MyAttriubute : Attribute {
   // individuelle Klassenmember
   public string name;
   public string id;
}

5.10.1.1 Selbstdefiniertes Attribut anwenden

Allgemein sind mit Attributen beliebige Elemente in .NET wie Assembly, Klasse und Member attributierbar. Über den Parameter AttributeTargets in AttributeUsage kann die Menge der attributierbaren Elemente eingeschränkt werden.

Die Attributierung eines Elements erfolgt beispielsweise so:

[MyAttribute(name="mko", id="1")]
public Class Anwendung {

    [MyAttribute(name="mak", id="2")]
    int TueWas() {
       return 99;
    }

}

In der Parameterliste der Attributdefinition können Felder in einer Kommaseparierten Liste dierekt Werte zugewiesen werden.

5.10.1.2 Attributeigenschaften auslesen

Die Klasse System.Attribute stellt statische Methoden Attribute.GetCustomAttribute(..) und Attribute.GetCustomAttributes(...) bereit. Diese liefern eine Referenz auf ein Attribut vom Typ object bzw. eine Referenz auf ein Array mit Attributen zurück. Als Eingaben erwarten die Funktionen das Type- Objekt der Instanz, deren Attribute ausgelesen werden soll, sowie den Typ des auszulesenden Attributes.

// Fall: Die Klasse Anwendung besitzt nur ein Attribut vom Typ MyAttribute

MyAttribute myAtt = (MyAttribute) Attribute.GetCustomAttribute(typeof(Anwendung), typeof(MyAttribute));

if (myAtt != null) {
   Console.WriteLine("name= {0}", myAtt.name);
   Console.WriteLine("id  = {0}", myAtt.id);
}