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);
}