Kapitel 31 Überblick über die .NET-Frameworks
Die .NET-Frameworks enthalten viele Funktionen,
die üblicherweise in sprachspezifischen Laufzeitbibliotheken enthalten sind. Es ist daher unerlässlich,
die in den Frameworks verfügbaren Klassen zu kennen.
31.1 Numerische Formatierung
 
Numerische Typen werden über die Mitgliedsfunktion
Format() des jeweiligen Datentyps formatiert. Diese Funktion
kann über String.Format() direkt aufgerufen werden,
wodurch die Format()-Funktion für jeden Datentyp aufgerufen
wird, oder über Console.WriteLine(), mit der String.Format()
aufgerufen wird.
Die Formatierung benutzerdefinierter Objekte wird
im Abschnitt »Benutzerdefinierte Objektformatierung« weiter
unten in diesem Kapitel besprochen. Im vorliegenden Abschnitt wird die
Formatierung integrierter Typen erläutert.
Es gibt zwei Methoden zur Festlegung der numerischen
Formatierung. Mit Hilfe einer Standardformatzeichenfolge kann ein numerischer
Typ in eine Zeichenfolgendarstellung konvertiert werden. Ist eine weiterführende
Steuerung der Ausgabe erforderlich, kann eine benutzerdefinierte Formatzeichenfolge
verwendet werden.
31.1.1 Standardformatzeichenfolgen
 
Eine Standardformatzeichenfolge umfasst ein Zeichen,
mit dem das Format angegeben wird, gefolgt von einer Ziffernsequenz,
mit der die Genauigkeit festgelegt wird. Es werden die folgenden Formate
unterstützt:
Formatzeichen
|
Beschreibung
|
C, c
|
Währung
|
D, d
|
Dezimal
|
E, e
|
Wissenschaftlich (exponentiell)
|
F, f
|
Festkomma
|
G, g
|
Allgemein
|
N, n
|
Zahl
|
X, x
|
Hexadezimal
|
Währung
Die Währungsformatzeichenfolge konvertiert einen numerischen Wert in eine Zeichenfolge
mit einem Währungswert für eine Ländereinstellung. Standardmäßig
werden die Formatinformationen durch die aktuelle Ländereinstellung bestimmt,
können aber durch Übergeben eines NumberFormatInfo-Objekts
geändert werden.
using System;
class Test
{
public static void Main()
{
Console.WriteLine("{0:C}", 33345.8977);
Console.WriteLine("{0:C}", -33345.8977);
}
}
Dieser Code erzeugt die folgende Ausgabe:
$33,345.90
($33,345.90)
Dezimal
Die Dezimalformatzeichenfolge konvertiert den numerischen
Wert in eine Ganzzahl. Die Mindestzahl an Kommastellen wird durch den
Genauigkeitsbezeichner festgelegt. Das Ergebnis wird von links mit Nullen
aufgefüllt, bis die erforderliche Stellenzahl erreicht ist.
using System;
class Test
{
public static void Main()
{
Console.WriteLine("{0:D}", 33345);
Console.WriteLine("{0:D7}", 33345);
}
}
Dieser Code erzeugt die folgende Ausgabe:
33345
0033345
Wissenschaftlich (exponentiell)
Die wissenschaftliche Formatzeichenfolge (exponentiell)
konvertiert den Wert in eine Zeichenfolge der Form
m.dddE+xxx
Vor dem Dezimalkomma befindet sich immer eine Stelle,
die Anzahl der Dezimalstellen wird durch den Genauigkeitsbezeichner
festgelegt und umfasst standardmäßig sechs Stellen. Der Formatbezeichner
steuert, ob in der Ausgabe E oder e erscheint.
using System;
class Test
{
public static void Main()
{
Console.WriteLine("{0:E}", 33345.8977);
Console.WriteLine("{0:E10}", 33345.8977);
Console.WriteLine("{0:e4}", 33345.8977);
}
}
Dieser Code erzeugt die folgende Ausgabe:
3.334590E+004
3.3345897700E+004
3.3346e+004
Festkomma
Die Festkommaformatzeichenfolge konvertiert den Wert in eine Zeichenfolge, bei
der die Anzahl der Stellen nach dem Dezimalkomma durch den Genauigkeitsbezeichner festgelegt wird.
using System;
class Test
{
public static void Main()
{
Console.WriteLine("{0:F}", 33345.8977);
Console.WriteLine("{0:F0}", 33345.8977);
Console.WriteLine("{0:F5}", 33345.8977);
}
}
Dieser Code erzeugt die folgende Ausgabe:
33345.90
33346
33345.89770
Allgemein
Die allgemeine Formatzeichenfolge konvertiert den
Wert entweder in das Festkomma- oder das wissenschaftliche Format, je
nachdem, welches kompakter ist.
using System;
class Test
{
public static void Main()
{
Console.WriteLine("{0:G}", 33345.8977);
Console.WriteLine("{0:G7}", 33345.8977);
Console.WriteLine("{0:G4}", 33345.8977);
}
}
Dieser Code erzeugt die folgende Ausgabe:
33345.8977
33345.9
3.335E4
Zahl
Die Zahlenformatzeichenfolge konvertiert den Wert in eine Zahl mit eingebetteten
Kommata, beispielsweise
12,345.11
Das Format kann durch Übergeben eines NumberFormatInfo-Objekts
an die Format()-Funktion gesteuert werden.
using System;
class Test
{
public static void Main()
{
Console.WriteLine("{0:N}", 33345.8977);
Console.WriteLine("{0:N4}", 33345.8977);
}
}
Dieser Code erzeugt die folgende Ausgabe:
33,345.90
33,345.8977
Hexadezimal
Die hexadezimale Formatzeichenfolge konvertiert
den Wert in das Hexadezimalformat. Die Mindestzahl an Kommastellen wird
durch den Genauigkeitsbezeichner festgelegt, die Zahl wird zum Erreichen
dieser Stellenzahl mit Nullen aufgefüllt.
Das Verwenden von X führt zu Großbuchstaben
im konvertierten Wert; x führt zur Kleinbuchstabenversion.
using System;
class Test
{
public static void Main()
{
Console.WriteLine("{0:X}", 255);
Console.WriteLine("{0:x8}", 1456);
}
}
Dieser Code erzeugt die folgende Ausgabe:
FF
000005b0
NumberFormatInfo
Die NumberFormatInfo-Klasse wird zum
Steuern der Zahlenformatierung eingesetzt. Durch das Setzen der Eigenschaften
in dieser Klasse kann der Programmierer das Währungssymbol, das
Dezimaltrennzeichen und andere Formatierungseigenschaften steuern.
31.1.2 Benutzerdefinierte Formatzeichenfolgen
 
Über die benutzerdefinierten Formatzeichenfolgen
erhält der Programmierer umfangreichere Steuerungsmöglichkeiten
über die Konvertierung, als dies mit den Standardformatzeichenfolgen möglich ist. Bei den benutzerdefinierten Formatzeichenfolgen
bilden Sonderzeichen die Vorlage für die Formatierung der betreffenden
Zahl. Alle Zeichen, die in der Formatzeichenfolge keine besondere Bedeutung
aufweisen, werden wörtlich in den Ausdruck kopiert.
Stellen- oder Nullplatzhalter
Das Nullzeichen (0) wird als Stellen-
oder Nullplatzhalter verwendet. Verfügt der numerische Wert an
der Position über eine Stelle, an der in der Formatzeichenfolge
die 0 erscheint, erscheint diese Stelle im Ergebnis. Falls
nicht, erscheint an dieser Stelle eine Null.
using System;
class Test
{
public static void Main()
{
Console.WriteLine("{0:000}", 55);
Console.WriteLine("{0:000}", 1456);
}
}
Dieser Code erzeugt die folgende Ausgabe:
055
1456
Stellen- oder Leerplatzhalter
Das Rautenzeichen (#) wird als Stellen- oder Leerplatzhalter
verwendet. Die Raute funktioniert genau wie der Nullplatzhalter (0),
abgesehen davon, dass ein Leerzeichen erscheint, wenn die Stelle nicht
belegt ist.
using System;
class Test
{
public static void Main()
{
Console.WriteLine("{0:#####}", 255);
Console.WriteLine("{0:#####}", 1456);
Console.WriteLine("{0:###}", 32767);
}
}
Dieser Code erzeugt die folgende Ausgabe:
255
1456
32767
Dezimalkomma
Der erste Punkt (.) in der Formatzeichenfolge
legt die Position des Dezimaltrennzeichens im Ergebnis fest. Das in der formatierten Zeichenfolge
als Dezimaltrennzeichen verwendete Zeichen wird durch eine NumberFormatInfo-Instanz
gesteuert.
using System;
class Test
{
public static void Main()
{
Console.WriteLine("{0:#####.000}", 75928.3);
Console.WriteLine("{0:##.000}", 1456.456456);
}
}
Dieser Code erzeugt die folgende Ausgabe:
75928.300
1456.456
Gruppentrennzeichen
Das Komma (,) wird als Gruppentrennzeichen
eingesetzt. Wenn ein Komma in der Mitte eines Anzeigestellenplatzhalters und links neben dem Dezimalkomma (falls vorhanden)
erscheint, wird ein Gruppentrennzeichen in die Zeichenfolge eingefügt.
Das in der formatierten Zeichenfolge verwendete Zeichen und die Anzahl
der zu gruppierenden Zahlen wird durch eine NumberFormatInfo-Instanz
gesteuert.
using System;
class Test
{
public static void Main()
{
Console.WriteLine("{0:##,###}", 2555634323);
Console.WriteLine("{0:##,000.000}", 14563553.593993);
Console.WriteLine("{0:#,#.000}", 14563553.593993);
}
}
Dieser Code erzeugt die folgende Ausgabe:
2,555,634,323
14,563,553.594
14,563,553.594
Vorskalierung von Zahlen
Mit dem Kommazeichen (,) kann auch
angegeben werden, dass die Zahl vorskaliert werden soll. Bei dieser
Verwendung muss das Komma direkt vor dem Dezimalkomma oder
am Ende der Formatzeichenfolge eingefügt werden.
Für jedes Komma an dieser Position wird die
Zahl vor der Formatierung durch 1 000 geteilt.
using System;
class Test
{
public static void Main()
{
Console.WriteLine("{0:000,.##}", 158847);
Console.WriteLine("{0:000,,,.###}", 1593833);
}
}
Dieser Code erzeugt die folgende Ausgabe:
158.85
000.002
Prozentnotierung
Mit dem Prozentzeichen (%) wird angegeben,
dass die anzuzeigende Zahl als Prozentwert dargestellt werden soll.
Die Zahl wird vor der Formatierung mit 100 multipliziert.
using System;
class Test
{
public static void Main()
{
Console.WriteLine("{0:##.000%}", 0.89144);
Console.WriteLine("{0:00%}", 0.01285);
}
}
Dieser Code erzeugt die folgende Ausgabe:
89.144%
01%
Exponentielle Notierung
Wenn in der Formatzeichenfolge direkt nach einem
#- oder einem 0-Platzhalter E+0,
E-0, e+0 oder e-0 erscheinen,
wird die Zahl in exponentieller Notierung formatiert. Die Anzahl der
Stellen im Exponenten wird durch die Anzahl der 0-Platzhalter
gesteuert, die im Exponentenbezeichner erscheinen. Das E
oder e wird direkt in die formatierte Zeichenfolge kopiert,
ein + bedeutet, dass an dieser Stelle ein Plus- oder Minuszeichen
erscheint, ein – bedeutet, dass dort nur ein Zeichen vorhanden
ist, wenn die Zahl negativ ist.
using System;
class Test
{
public static void Main()
{
Console.WriteLine("{0:###.000E-00}", 3.1415533E+04);
Console.WriteLine("{0:#.0000000E+000}", 2.553939939E+101);
}
}
Dieser Code erzeugt die folgende Ausgabe:
314.155E-02
2.5539399E+101
Abschnittstrennzeichen
Das Semikolon (;) wird dazu verwendet,
verschiedene Formatzeichenfolgen für eine Zahl festzulegen, je
nachdem, ob die Zahl positiv, negativ oder gleich Null ist. Sind nur
zwei Abschnitte vorhanden, gilt der erste Abschnitt für positive
und Nullwerte, der zweite Abschnitt wird auf negative Werte angewendet.
Sind drei Abschnitte vorhanden, werden diese auf positive Werte, Nullwerte
und negative Werte angewendet.
using System;
class Test
{
public static void Main()
{
Console.WriteLine("{0:###.00;0;(###.00)}", -456.55);
Console.WriteLine("{0:###.00;0;(###.00)}", 0);
Console.WriteLine("{0:###.00;0;(###.00)}", 456.55);
}
}
Dieser Code erzeugt die folgende Ausgabe:
457
(.00)
456.55
Escapezeichen und Literale
Der umgekehrte Schrägstrich (\)
kann angegeben werden, damit Zeichen nicht als Formatierungszeichen
interpretiert werden. Da der umgekehrte Schrägstrich in C#-Literalen
bereits eine Bedeutung trägt, ist es einfacher, die Zeichenfolge
mit Hilfe der wörtlichen Literalsyntax anzugeben; andernfalls ist
ein doppelter umgekehrter Schrägstrich (\\) zur Erzeugung
eines einzelnen umgekehrten Schrägstriches in der Ausgabezeichenfolge
erforderlich.
Sie können eine Zeichenfolge, deren Zeichen
nicht interpretiert werden sollen, auch in einfache Anführungszeichen
einschließen, diese Methode ist möglicherweise einfacher als
die Verwendung von \.
using System;
class Test
{
public static void Main()
{
Console.WriteLine("{0:###\\#}", 255);
Console.WriteLine(@"{0:###\#}", 255);
Console.WriteLine("{0:###'#0%;'}", 1456);
}
}
Dieser Code erzeugt die folgende Ausgabe:
255#
255#
1456#0%;
31.2 Formatierung von Datum und Uhrzeit
 
Die Klasse DateTime stellt flexible
Formatierungsoptionen bereit. Es können verschiedene Einzelzeichenformate
angegeben werden, des Weiteren wird die benutzerdefinierte Formatierung
unterstützt.
Standardmäßige DateTime-Formate
Zeichen
|
Muster
|
Beschreibung
|
d
|
MM/dd/yyyy
|
ShortDatePattern
|
D
|
dddd, MMMM dd, yyy
|
LongDatePattern
|
f
|
dddd, MMMM dd, YYYY HH:mm
|
Full (langes Datum + kurze Uhrzeit)
|
F
|
dddd, MMMM dd, yyyy HH:mm:ss
|
FullDateTimePattern (langes Datum + lange Uhrzeit)
|
g
|
MM/dd/yyyy HH:mm
|
General (kurzes Datum + kurze Uhrzeit)
|
G
|
MM/dd/yyyy HH:mm:ss
|
General (kurzes Datum + lange Uhrzeit)
|
m, M
|
MMMM dd
|
MonthDayPattern
|
r, R
|
ddd, dd MMM yy HH’:’mm’:’ss
’GMT’
|
RFC1123Pattern
|
s
|
yyyy-MM-dd HH:mm:ss
|
SortableDateTimePattern (ISO 8601)
|
S
|
YYYY-mm-DD hh:MM:SS GMT
|
Sortierbar mit Zeitzoneninformationen
|
t
|
HH:mm
|
ShortTimePattern
|
T
|
HH:mm:ss
|
LongTimePattern
|
u
|
yyyy-MM-dd HH:mm:ss
|
Wie »s«, aber mit Greenwich-Zeit, nicht
der lokalen Zeit
|
U
|
dddd, MMMM dd, yyyy HH:mm:ss
|
UniversalSortableDateTimePattern
|
Benutzerdefinierte DateTime-Formate
Zur Erstellung eines benutzerdefinierten Formats
können folgende Muster eingesetzt werden:
Muster
|
Beschreibung
|
d
|
Tag des Monats als Zahl, ohne vorangestellte Null
bei einstelligen Tagen
|
dd
|
Tag des Monats als Zahl, mit vorangestellter Null
bei einstelligen Tagen
|
ddd
|
Tag der Woche als aus drei Buchstaben bestehende
Abkürzung
|
dddd
|
Tag der Woche mit vollständigem Namen
|
M
|
Monat als Zahl, ohne vorangestellte Null bei einstelligen
Monaten
|
MM
|
Monat als Zahl mit vorangestellter Null
|
MMM
|
Monat als aus drei Buchstaben bestehende Abkürzung
|
MMMM
|
Monat mit vollständigem Namen
|
y
|
Jahr als zweistellige Zahl (letzte zwei Ziffern),
keine vorangestellte Null
|
yy
|
Jahr als zweistellige Zahl (letzte zwei Ziffern),
mit vorangestellter Null
|
yyyy
|
Jahr in vierstelliger Darstellung
|
Die Namen der Tage und Wochen werden durch das geeignete
Feld in der DateTimeFormatInfo-Klasse festgelegt.
31.3 Benutzerdefinierte Objektformatierung
 
In einigen der bisher verwendeten Beispiele wurde
die ToString()-Funktion außer Kraft gesetzt, um eine
Zeichenfolgendarstellung einer Funktion bereitzustellen. Ein Objekt
kann verschiedene Formate bereitstellen, indem die IFormattable-Schnittstelle
definiert wird. Anschließend kann die Darstellung basierend auf
der Zeichenfolge der Funktion geändert werden.
Eine Employee-Klasse könnte beispielsweise
durch eine andere Formatzeichenfolge zusätzliche Informationen
bereitstellen.
using System;
class Employee
{
public Employee(int id, string firstName, string lastName)
{
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
}
public string Format (string format, IServiceObjectProvider sop)
{
if (format.Equals("F"))
return(String.Format("{0}: {1}, {2}",
id, lastName, firstName));
else
return(id.Format(format, sop));
}
int id;
string firstName;
string lastName;
}
class Test
{
public static void Main()
{
Employee fred = new Employee(123, "Fred", "Morthwaite");
Console.WriteLine("No format: {0}", fred);
Console.WriteLine("Full format: {0:F}", fred);
}
}
Die Format()-Funktion sucht nach dem
F-Format. Ist dieses vorhanden, werden die vollständigen
Informationen ausgeschrieben. Kann dieses Formatzeichen nicht ermittelt
werden, wird das Standardobjektformat verwendet.
Die Main()-Funktion übergibt das
Formatflag im zweiten WriteLine()-Aufruf.
31.3.1 Neue Formatierung für vorhandene Typen
 
Es ist auch möglich, vorhandenen Objekten ein
neues Format zuzuweisen. Beim folgenden Objekt können float-
und double-Werte auch in Ångström formatiert
werden. Ein Ångström entspricht 10E-10 Metern.
using System;
public class AngstromFormatter: IServiceObjectProvider, ICustomFormatter
{
public object GetServiceObject(Type service)
{
if (service == typeof(ICustomFormatter))
return this;
else
return null;
}
public string Format(string format, object arg,
IServiceObjectProvider sop)
{
if (format == null)
return(String.Format("{0}", arg));
if (format.StartsWith("Ang"))
{
// Zusätzliche Formatierungsinformationen
// nach "Ang" hier extrahieren…
string extra = "";
if (arg is float)
{
float f = (float) arg;
f *= 1.0E10F;
return(String.Format("{0:" + extra + "}", f) + " Å");
}
else if (arg is double)
{
double d = (double) arg;
d *= 1.0E10D;
return(String.Format("{0:" + extra + "}", d) + " Å");
}
}
// Kein unterstütztes Objekt oder Format
return(String.Format("{0:" + format + "}", arg));
}
}
class Test
{
public static void Main()
{
AngstromFormatter angstrom = new AngstromFormatter();
Console.WriteLine("Meters: {0}", 1.35E-8F, angstrom);
Console.WriteLine(String.Format("Angstroms: {0:Ang}",
new object[] {1.35E-8F}, angstrom));
Console.WriteLine(String.Format("Angstroms: {0:Ang:g}",
new object[] {3.59393E-9D}, angstrom));
}
}
In diesem Beispiel unterstützt die AngstromFormatter-Klasse
das Formatieren von Zahlen in Ångström, indem die Werte durch
10E-10 dividiert werden und anschließend das Ångström-Symbol »Å« an die Zeichenfolge gehängt
wird. Nach dem Ang-Format können weitergehende Formatierungsinformationen
angegeben werden, um die Anzeige der Gleitkommazahl zu steuern.
31.4 Numerische Syntaxanalyse
 
Zahlen werden mit Hilfe der für numerische
Datentypen verfügbaren Parse()-Methode analysiert.
Durch Übergeben von Flags der Klasse NumberStyle können
die erlaubten Ausdrucksweisen angegeben werden, mit einer
Instanz von NumberFormatInfo kann die Syntaxanalyse gesteuert
werden.
Bei den über die standardmäßigen
Formatbezeichner erzeugten numerischen Zeichenfolgen (ausgeschlossen
der Hexadezimalformatbezeichner) verläuft die Syntaxanalyse immer erfolgreich,
wenn NumberStyles.Any angegeben wird.
using System;
class Test
{
public static void Main()
{
int value = Int32.Parse("99953");
double dval = Double.Parse("1.3433E+35");
Console.WriteLine("{0}", value);
Console.WriteLine("{0}", dval);
}
}
Dieser Code erzeugt die folgende Ausgabe:
99953
1.3433E35
31.5 XML-Verwendung in C#
 
Obwohl C# die XML-Dokumentation unterstützt
(siehe Kapitel 31, C# im Detail, Abschnitt »XML-Dokumentation«),
bietet C# keine Sprachunterstützung für die Verwendung von
XML.
Das ist aber okay, denn die Common Language Runtime bietet umfangreiche XML-Unterstützung. In
diesem Themenbereich sind besonders die Namespaces System.Data.Xml
und System.Xml von Interesse.
31.6 Eingabe/Ausgabe
 
Die .NET Common Language Runtime stellt über den Sytem.IO-Namespace
E/A-Funktionen bereit. Dieser Namespace enthält Klassen für
die Ein- und Ausgabe sowie für E/A-bezogene Funktionen, beispielsweise
für das Durchsuchen von Verzeichnissen, die Dateiüberwachung
usw.
Lese- und Schreiboperationen werden mit Hilfe der
Stream-Klasse ausgeführt, mit der lediglich beschrieben
wird, wie Bytes in eine Art Sicherungsspeicher gelesen und geschrieben
werden können. Stream ist eine abstrakte Klasse, daher
werden tatsächlich von Stream abgeleitete
Klassen verwendet. Es sind folgende Klassen verfügbar:
Von Stream abgeleitete E/A-Klassen:
Klasse
|
Beschreibung
|
FileStream
|
Ein Stream einer Datenträgerdatei
|
MemoryStream
|
Ein im Speicher gespeicherter Stream
|
NetworkStream
|
Ein Stream für eine Netzwerkverbindung
|
BufferedStream
|
Implementiert einen Puffer oberhalb eines anderen
Streams
|
Mit Ausnahme von BufferedStream, der
sich oberhalb eines anderen Streams befindet, wird durch jeden Stream
der Bestimmungsort der Daten definiert.
Die Stream-Klasse stellt Funktionen
zum Lesen und Schreiben auf Byteebene bereit, sowohl für synchrone
als auch asynchrone Operationen. Es ist jedoch günstiger, oberhalb
des Streams über eine Schnittstelle höherer Ebene zu verfügen.
Hier stehen einige Schnittstellen zur Verfügung, die je nach gewünschtem
Endformat ausgewählt werden können.
31.6.1 Binärklassen
 
Die Klassen BinaryReader und BinaryWriter
werden zum Lesen und Schreiben von Werten im Binärformat (Rohformat)
verwendet. Beispielsweise kann BinaryWriter zum Schreiben
von int-Werten eingesetzt werden, gefolgt von float
und einem weiteren int.
Diese Klassen werden in der Regel – keine Überraschung
– zum Lesen und Schreiben binärer Formate eingesetzt und operieren
auf Streamebene.
31.6.2 Textklassen
 
Die abstrakten Klassen TextReader und
TextWriter definieren, wie Text gelesen und geschrieben
wird. Sie ermöglichen Operationen für Zeichen, Zeilen, Blöcke
usw. Es sind zwei verschiedene Implementierungen von TextReader
verfügbar.
Die StreamWriter-Klasse wird für
»normale« E/A-Operationen verwendet (Öffnen von Dateien,
Auslesen von Zeilen) und operiert auf Streamebene (Stream).
Die Klassen StringReader und StringWriter
können zum Lesen und Schreiben von Zeichenfolgen verwendet werden.
31.6.3 XML
 
Die Klassen XmlTextReader und XmlTextWriter werden für XML-Lese- und Schreiboperationen
eingesetzt. Sie ähneln vom Entwurf her den Klassen TextReader und TextWriter, sind aber nicht von diesen Klassen abgeleitet,
da sie keinen Text, sondern XML-Einheiten handhaben. Es handelt sich um Lowlevelklassen, die zum Erstellen oder Decodieren von XML verwendet
werden.
31.6.4 Lese- und Schreibvorgänge für Dateien
 
Es gibt zwei Möglichkeiten, Streams mit Dateiverbindungen
zu erhalten. Die erste Möglichkeit besteht darin, die FileStream-Klasse
zu verwenden, die vollständige Steuerung über den Dateizugriff
bietet, Zugriffsmodus, gemeinsame Verwendung und Puffer eingeschlossen.
using System;
using System.IO;
class Test
{
public static void Main()
{
FileStream f = new FileStream("output.txt", FileMode.Create);
StreamWriter s = new StreamWriter(f);
s.WriteLine("{0} {1}", "test", 55);
s.Close();
f.Close();
}
}
Statt dessen können auch die Funktionen der
Klasse File dazu verwendet werden, einen Stream für
eine Datei zu erhalten. Diese Methode ist besonders geeignet, wenn bereits
ein File-Objekt mit den Dateiinformationen zur Verfügung
steht, wie in der PrintFile()-Funktion im nächsten
Beispiel.
31.6.5 Durchsuchen von Verzeichnissen
 
Dieses Beispiel zeigt, wie eine Verzeichnisstruktur durchsucht wird. Es wird eine DirectoryWalker-Klasse
definiert, die für jedes Verzeichnis und jede Datei Zuweisungen
sowie einen zu durchlaufenden Pfad enthält.
using System;
using System.IO;
public class DirectoryWalker
{
public delegate void ProcessDirCallback(Directory dir, int level,
object obj);
public delegate void ProcessFileCallback(File file, int level, object
obj);
public DirectoryWalker( ProcessDirCallback dirCallback,
ProcessFileCallback fileCallback)
{
this.dirCallback = dirCallback;
this.fileCallback = fileCallback;
}
public void Walk(string rootDir, object obj)
{
DoWalk(new Directory(rootDir), 0, obj);
}
void DoWalk(Directory dir, int level, object obj)
{
foreach (FileSystemEntry d in dir.GetFileSystemEntries ())
{
if (d is File)
{
if (fileCallback != null)
fileCallback((File) d, level, obj);
}
else
{
if (dirCallback != null)
dirCallback((Directory) d, level, obj);
DoWalk((Directory) d, level + 1, obj);
}
}
}
ProcessDirCallback dirCallback;
ProcessFileCallback fileCallback;
}
class Test
{
public static void PrintDir(Directory d, int level, object obj)
{
WriteSpaces(level * 2);
Console.WriteLine("Dir: {0}", d.FullName);
}
public static void PrintFile(File f, int level, object obj)
{
WriteSpaces(level * 2);
Console.WriteLine("File: {0}", f.FullName);
}
public static void WriteSpaces(int spaces)
{
for (int i = 0; i < spaces; i++)
Console.Write(" ");
}
public static void Main(string[] args)
{
DirectoryWalker dw = new DirectoryWalker(
new DirectoryWalker.ProcessDirCallback(PrintDir),
new DirectoryWalker.ProcessFileCallback(PrintFile));
string root = ".";
if (args.Length == 1)
root = args[0];
dw.Walk(root, "Passed string object");
}
}

31.7 Serialisierung
 
Die Serialisierung ist der Vorgang, mit dem die
Laufzeitumgebung Objekte in einer Art Speicher speichert oder von
einem Standort an einen anderen überträgt.
Die Metadaten eines Objekts enthalten genügend
Informationen, um der Laufzeitumgebung die Serialisierung der Felder
zu ermöglichen, hierbei benötigt die Laufzeitumgebung jedoch
ein wenig Unterstützung.
Diese Hilfe wird über zwei Attribute bereitgestellt.
Über das Attribut [Serializable] wird gekennzeichnet,
dass ein Objekt serialisiert werden kann. Das Attribut [NonSerialized] kann auf Felder oder Eigenschaften
angewendet werden, die nicht serialisiert werden sollten. Dies ist sinnvoll,
wenn es sich um einen Cachewert oder einen abgeleiteten Wert handelt.
Im folgenden Beispiel liegt eine Containerklasse namens MyRow vor, die Elemente der
Klasse MyElements beinhaltet. Das Feld cacheValue
in MyElement ist mit dem Attribut [NonSerialized]
gekennzeichnet, damit es nicht serialisiert wird.
Im Beispiel wird das MyRow-Objekt serialisiert,
in ein binäres Format gebracht und anschließend in ein XML-Format
umgewandelt.
// Datei: serial.cs
// Kompilieren mit: csc serial.cs /r:system.runtime.serialization.formatters.soap.dll
using System;
using System.IO;
using System.Collections;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization.Formatters.Soap;
[Serializable]
public class MyElement
{
public MyElement(string name)
{
this.name = name;
this.cacheValue = 15;
}
public override string ToString()
{
return(String.Format("{0}: {1}", name, cacheValue));
}
string name;
// Dieses Feld wird nicht beibehalten
[NonSerialized]
int cacheValue;
}
[Serializable]
public class MyRow
{
public void Add(MyElement my)
{
row.Add(my);
}
public override string ToString()
{
string temp = null;
foreach (MyElement my in row)
temp += my.ToString() + "\n";
return(temp);
}
ArrayList row = new ArrayList();
}
class Test
{
public static void Main()
{
MyRow row = new MyRow();
row.Add(new MyElement("Gumby"));
row.Add(new MyElement("Pokey"));
Console.WriteLine("Initial value");
Console.WriteLine("{0}", row);
// Umschreiben in binären Wert, erneut einlesen
Stream streamWrite = File.Create("MyRow.bin");
BinaryFormatter binaryWrite = new BinaryFormatter();
binaryWrite.Serialize(streamWrite, row);
streamWrite.Close();
Stream streamRead = File.OpenRead("MyRow.bin");
BinaryFormatter binaryRead = new BinaryFormatter();
MyRow rowBinary = (MyRow) binaryRead.Deserialize(streamRead);
streamRead.Close();
Console.WriteLine("Values after binary serialization");
Console.WriteLine("{0}", rowBinary);
// Umschreiben in SOAP (XML), erneut einlesen
streamWrite = File.Create("MyRow.xml");
SoapFormatter soapWrite = new SoapFormatter();
soapWrite.Serialize(streamWrite, row);
streamWrite.Close();
streamRead = File.OpenRead("MyRow.xml");
SoapFormatter soapRead = new SoapFormatter();
MyRow rowSoap = (MyRow) soapRead.Deserialize(streamRead);
streamRead.Close();
Console.WriteLine("Values after SOAP serialization");
Console.WriteLine("{0}", rowSoap);
}
}
Dieser Code erzeugt die folgende Ausgabe:
Initial value
Gumby: 15
Pokey: 15
Values after binary serialization
Gumby: 0
Pokey: 0
Values after SOAP serialization
Gumby: 0
Pokey: 0
Das Feld cacheValue wurde nicht beibehalten,
da es mit [NonSerialized] gekennzeichnet war. Die Datei
MyRow.Bin enthält die binäre Serialisierung,
die Datei MyRow.xmlb enthält die XML-Version.
Die XML-Codierung ist eine SOAP-Verschlüsselung (Symbolic Optimizer Assembly Program). Zum Erzeugen einer spezifischen XML-Codierung
kann die XmlSerializer-Klasse verwendet werden.
31.8 Threading
 
Der System.Threading-Namespace enthält
nützliche Klassen für das Threading und die Synchronisierung.
Der geeignete Typ für Synchronisierung und/oder Ausschluss richtet
sich nach dem Programmentwurf, C# unterstützt jedoch den einfachen
Ausschluss über die lock-Anweisung.
Lock verwendet die System.Threading.Monitor-Klasse
und bietet ähnliche Funktionalität für CriticalSection-Aufrufe
in Win32.
Im folgenden Beispiel wird die Erhöhung eines
Kontensaldos simuliert. Der Code, mit dem die Saldoerhöhung erfolgt,
ruft zunächst den aktuellen Kontostand in einer temporären
Variablen ab und verweilt für eine Millisekunde. Die Wahrscheinlichkeit,
dass während dieser Ruhephase – also bevor der erste Thread
reaktiviert wird und den neuen Wert speichert – ein anderer Thread
den Saldo abruft, ist sehr hoch.
Falls der Code wie geschrieben ausgeführt wird,
liegt der Endwert unter dem erwarteten Ergebnis von 1.000. Durch Entfernen
der Kommentare zur lock-Anweisung in der Deposit()-Funktion
stellt das System sicher, dass sich im lock-Block immer
nur ein Thread befindet, und das Endergebnis ist korrekt.
Das an die lock-Anweisung übergebene
Objekt muss vom Verweistyp sein und sollte den zu schützenden Wert
enthalten. Das Sperren der aktuellen Instanz mit this verhindert
einen Zugriff durch die gleiche Instanz.
using System;
using System.Threading;
public class Account
{
public Account(decimal balance)
{
this.balance = balance;
}
public void Deposit(decimal amount)
{
//lock(this) // Auskommentieren, um Block zu schützen
{
Decimal temp = balance;
temp += amount;
Thread.Sleep(1); // vorgesehene Ruhephase
balance = temp;
}
}
public Decimal Balance
{
get
{
return(balance);
}
}
decimal balance;
}
class ThreadTest
{
public void MakeDeposit()
{
for (int i = 0; i < 10; i++)
account.Deposit(10);
}
public static void Main(string[] args)
{
ThreadTest b = new ThreadTest();
Thread t = null;
// 10 Threads erstellen
for (int threads = 0; threads < 10; threads++)
{
t = new Thread(new ThreadStart(b.MakeDeposit));
t.Start();
}
t.Join(); // Warten, bis die letzten drei Threads abgeschlossen
sind
Console.WriteLine("Balance: {0}", b.account.Balance);
}
Account account = new Account(0);
}
31.9 Lesen von Webseiten
 
Das folgende Beispiel veranschaulicht, wie unter
Verwendung von C# ein so genannter »Screenscraper« geschrieben wird. Der folgende Code enthält
ein Aktiensymbol, formatiert eine URL-Adresse zum Abrufen eines Angebots von der Microsoft MoneyCentral-Site
und extrahiert anschließend das Angebot mit Hilfe eines regulären
Ausdrucks aus der HTML-Seite.
// Datei: quote.cs
// Kompilieren mit: csc quote.cs /r:system.net.dll /r:system.text.regularexpressions.dll
using System;
using System.Net;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
class QuoteFetch
{
public QuoteFetch(string symbol)
{
this.symbol = symbol;
}
public string Last
{
get
{
string url = "http://moneycentral.msn.com/scripts/
webquote.dll?ipage=qd&Symbol=";
url += symbol;
ExtractQuote(ReadUrl(url));
return(last);
}
}
string ReadUrl(string url)
{
URI uri = new URI(url);
// Angefordertes Objekt erstellen
WebRequest req = WebRequestFactory.Create(uri);
WebResponse resp = req.GetResponse();
Stream stream = resp.GetResponseStream();
StreamReader sr = new StreamReader(stream);
string s = sr.ReadToEnd();
return(s);
}
void ExtractQuote(string s)
{
// Zeile wie: "Last</TD><TD ALIGN=RIGHT NOWRAP><B> 78
3/16"?
Regex lastmatch = new Regex(@"Last\D+(?<last>.+)<\/B>");
last = lastmatch.Match(s).Group(1).ToString();
}
string symbol;
string last;
}
class Test
{
public static void Main(string[] args)
{
if (args.Length != 1)
Console.WriteLine("Quote <symbol>");
else
{
QuoteFetch q = new QuoteFetch(args[0]);
Console.WriteLine("{0} = {1}", args[0], q.Last);
}
}
}
|