Kapitel 18 Zeichenfolgen
Alle Zeichenfolgen in C# stellen Instanzen des System.String-Typs
der Common Language Runtime dar. Aufgrund dieser Tatsache stehen viele
integrierte Operationen für Zeichenfolgen zur Verfügung. Die
String-Klasse definiert beispielsweise eine Indizierungsfunktion, mit der die Zeichen einer Zeichenfolge durchlaufen
werden können:
using System;
class Test
{
public static void Main()
{
string s = "Test String";
for (int index = 0; index < s.Length; index++)
Console.WriteLine("Char: {0}", s[index]);
}
}
18.1 Operationen
 
Die string-Klasse ist ein Beispiel
für einen unveränderlichen Typ, d. h., die in der Zeichenfolge
enthaltenen Zeichen können von den Benutzern der Zeichenfolge nicht
geändert werden. Alle Operationen, die von der Zeichenfolgenklasse
ausgeführt werden, geben eine geänderte Version der Zeichenfolge
zurück, anstatt die Instanz zu bearbeiten, für die die Methode
aufgerufen wird.
Die String-Klasse unterstützt
die folgenden Vergleichs- und Suchfunktionen:
Element
|
Beschreibung
|
Compare()
|
Vergleich zweier Zeichenfolgen.
|
CompareOrdinal()
|
Vergleich zweier Zeichenfolgenbereiche mit Hilfe
eines Ordnungszahlvergleichs.
|
CompareTo()
|
Vergleich der aktuellen Instanz mit einer anderen
Instanz.
|
EndsWith()
|
Ermittelt, ob am Ende einer Zeichenfolge eine Teilzeichenfolge
vorhanden ist.
|
StartsWith()
|
Ermittelt, ob am Anfang einer Zeichenfolge eine
Teilzeichenfolge vorhanden ist.
|
IndexOf()
|
Gibt die Position des ersten Auftretens einer Teilzeichenfolge
zurück.
|
LastIndexOf()
|
Gibt die Position des letzten Auftretens einer Teilzeichenfolge
zurück.
|
Die String-Klasse unterstützt die folgenden Bearbeitungsmethoden:
Element
|
Beschreibung
|
Concat()
|
Verkettet zwei oder mehr Zeichenfolgen miteinander.
Werden Objekte übergeben, wird für diese die Funktion
ToString() aufgerufen.
|
CopyTo()
|
Kopiert eine bestimmte Anzahl Zeichen von einer
Position in der Zeichenfolge in ein Array.
|
Insert()
|
Gibt eine neue Zeichenfolge zurück, bei der
an einer bestimmten Position eine Teilzeichenfolge eingefügt wurde.
|
Join()
|
Fügt ein Array aus Zeichenfolgen zusammen,
bei dem zwischen den Arrayelementen ein Trennzeichen steht.
|
PadLeft()
|
Richtet eine Zeichenfolge links im Feld aus.
|
PadRight()
|
Richtet eine Zeichenfolge rechts im Feld aus.
|
Remove()
|
Löscht Zeichen aus einer Zeichenfolge.
|
Replace()
|
Ersetzt alle Instanzen eines Zeichens durch ein
anderes Zeichen.
|
Split()
|
Erstellt ein Array aus Zeichenfolgen, indem eine
Zeichenfolge bei jedem Auftreten mindestens eines Zeichens getrennt
wird.
|
Substrng()
|
Extrahiert eine Teilzeichenfolge aus einer Zeichenfolge.
|
ToLower()
|
Gibt eine Zeichenfolge in Kleinschreibung zurück.
|
ToUpper()
|
Gibt eine Zeichenfolge in Großschreibung zurück.
|
Trim()
|
Entfernt Leerbereiche aus einer Zeichenfolge.
|
TrimEnd()
|
Entfernt eine Zeichenkette am Ende einer Zeichenfolge.
|
TrimStart()
|
Entfernt eine Zeichenkette am Anfang einer Zeichenfolge.
|

18.2 Konvertieren von Objekten in Zeichenfolgen
 
Die Funktion object.ToString() wird
durch die integrierten Typen außer Kraft gesetzt, damit ein Wert
leicht in eine Zeichenfolgendarstellung dieses Wertes konvertiert werden
kann. Das Aufrufen von ToString() führt zu einer standardmäßigen
Darstellung des Wertes; eine andere Darstellung kann durch den Aufruf
von String.Format() erreicht werden. Weitere Informationen
zu diesem Thema finden Sie im Abschnitt zur Formatierung in Kapitel 30,
Überblick über die .NET-Frameworks.
18.3 Ein Beispiel
 
Die Teilungsfunktion kann dazu verwendet werden, eine Zeichenfolge in
Teilzeichenfolgen mit Trennzeichen aufzuteilen.
using System;
class Test
{
public static void Main()
{
string s = "Oh, I hadn't thought of that";
char[] separators = new char[] {' ', ','};
foreach (string sub in s.Split(separators))
{
Console.WriteLine("Word: {0}", sub);
}
}
}
Dieser Code erzeugt die folgende Ausgabe:
Word: Oh
Word:
Word: I
Word: hadn't
Word: thought
Word: of
Word: that
Das separators-Zeichenarray definiert, in welche Zeichen die Zeichenfolge aufgeteilt
wird. Die Split()-Funktion gibt ein Array aus Zeichenfolgen
zurück, die foreach-Anweisung durchläuft das
Array und erzeugt die Ausgabe.
In diesem Fall ist die Ausgabe nicht besonders nützlich,
da das Kommazeichen zweimal geteilt wird. Dies kann mit Hilfe der herkömmlichen
Ausdrucksklassen behoben werden.
18.4 StringBuilder
 
Obwohl mit der Funktion String.Format()
eine Zeichenfolge erstellt werden kann, die auf den Werten anderer Zeichenfolgen
basiert, ist dies nicht die effizienteste Methode zur Zusammenfügung
von Zeichenfolgen. Die Laufzeitumgebung stellt die Klasse StringBuilder bereit,
mit der dieser Vorgang vereinfacht werden kann.
Die StringBuilder-Klasse unterstützt
die folgenden Eigenschaften und Methoden:
Eigenschaft
|
Beschreibung
|
Capacity
|
Legt die Anzahl der Zeichen fest, die StringBuilder
enthalten kann bzw. ruft diese ab.
|
[]
|
Der Indizierer StringBuilder wird dazu
verwendet, ein Zeichen an einer bestimmten Position zu setzen oder abzurufen.
|
Length
|
Legt die Länge fest bzw. ruft diese ab.
|
MaxCapacity
|
Ruft die maximale Kapazität von StringBuilder
ab.
|
Methode
|
Beschreibung
|
Append()
|
Hängt die Zeichenfolgendarstellung eines Objekts
an.
|
AppendFormat()
|
Hängt unter Verwendung einer spezifischen Formatzeichenfolge
eine Zeichenfolgendarstellung eines Objekts an.
|
EnsureCapacity()
|
Stellt sicher, dass StringBuilder über
genügend Platz für eine bestimmte Zeichenzahl verfügt.
|
Insert()
|
Fügt die Zeichenfolgendarstellung eines angegebenen
Objekts an einer spezifischen Position ein.
|
Remove()
|
Entfernt die angegebenen Zeichen.
|
Replace()
|
Ersetzt alle Instanzen eines Zeichens durch ein
neues Zeichen.
|
Das folgende Beispiel verdeutlicht, wie die Klasse
StringBuilder zum Erstellen einer Zeichenfolge aus separaten
Zeichenfolgen eingesetzt werden kann.
using System;
using System.Text;
class Test
{
public static void Main()
{
string s = "I will not buy this record, it is scratched";
char[] separators = new char[] {' ', ','};
StringBuilder sb = new StringBuilder();
int number = 1;
foreach (string sub in s.Split(separators))
{
sb.AppendFormat("{0}: {1} ", number++, sub);
}
Console.WriteLine("{0}", sb);
}
}
Dieser Code erzeugt eine Zeichenfolge mit nummerierten
Wörtern und führt zur folgenden Ausgabe:
1: I 2: will 3: not 4: buy 5: this 6: record 7:
8: it 9: is 10: scratched
Da über den Aufruf von split()
sowohl das Leerzeichen als auch das Komma als Trennzeichen festgelegt
wird, wird angenommen, dass sich zwischen Komma und Leerzeichen ein
Wort befindet, was zu einem leeren Eintrag führt.
18.5 Reguläre Ausdrücke
 
Wenn die Suchfunktionen der String-Klasse
nicht ausreichen, kann auf die reguläre Ausdrucksklasse Regex
des System.Text-Namespace zurückgegriffen werden.
Reguläre Ausdrücke bieten eine sehr leistungsfähige Methode
für Such- und/oder Ersetzungsläufe.
Obwohl an dieser Stelle einige Beispiele zur Verwendung
dieser regulären Ausdrücke aufgezeigt werden sollen, kann
im Rahmen dieses Buches keine erschöpfende Beschreibung gegeben
werden. Zu diesem Thema stehen verschiedene Bücher zur Verfügung,
darüber hinaus wird dieser Themenbereich in nahezu jedem Perl-Handbuch
behandelt.
Die regulären Ausdrucksklassen sind eine recht
interessante Methode zur Leistungsoptimierung. Statt den regulären
Ausdruck für jede Übereinstimmung zu interpretieren, wird
ein kurzes Programm zur Implementierung der Übereinstimmung des
regulären Ausdrucks geschrieben, und dieser Code wird erneut ausgeführt1 .
Das vorstehende Beispiel mit Verwendung von Split()
kann mit Hilfe eines regulären Ausdrucks umgeschrieben werden.
Hierbei wird nicht über einzelne Zeichen festgelegt, wie die Teilung
erfolgen soll, sondern über den regulären Ausdruck. Auf diese
Weise wird der leere Eintrag des vorangegangenen Beispiels entfernt.
// Datei: regex.cs
// Kompilieren mit: csc /r:system.text.regularexpressions.dll regex.cs
using System;
using System.Text.RegularExpressions;
class Test
{
public static void Main()
{
string s = "Oh, I hadn't thought of that";
Regex regex = new Regex(@"( |, )");
char[] separators = new char[] {' ', ','};
foreach (string sub in regex.Split(s))
{
Console.WriteLine("Word: {0}", sub);
}
}
}
Dieser Code erzeugt die folgende Ausgabe:
Word: Oh
Word: I
Word: hadn't
Word: thought
Word: of
Word: that
Im regulären Ausdruck wird die Zeichenfolge
entweder basierend auf einem Leerzeichen oder einem Komma mit nachfolgendem
Leerzeichen geteilt.
18.5.1 Komplexere Syntaxanalyse
 
Der Einsatz regulärer Ausdrücke zur Verbesserung
der Funktion von Split() demonstriert nicht wirklich deren
Leistungsfähigkeit. Im folgenden Beispiel werden reguläre
Ausdrücke zur Syntaxanalyse einer IIS-Protokolldatei verwendet. Die Protokolldatei sieht in etwa so
aus:
#Software: Microsoft Internet Information Server
4.0
#Version: 1.0
#Date: 1999-12-31 00:01:22
#Fields: time c-ip cs-method cs-uri-stem sc-status
00:01:31 157.56.214.169 GET /Default.htm 304
00:02:55 157.56.214.169 GET /docs/project/overview.htm 200
Der folgende Code setzt dies in eine etwas brauchbarere
Form um.
// Datei: logparse.cs
// Kompilieren mit: csc logparse.cs /r:system.net.dll /r:system.text.regularexpressions.dll
using System;
using System.Net;
using System.IO;
using System.Text.RegularExpressions;
using System.Collections;
class Test
{
public static void Main(string[] args)
{
if (args.Length == 0) // Wir benötigen eine zu analysierende
Datei
{
Console.WriteLine("No log file specified.");
}
else
ParseLogFile(args[0]);
}
public static void ParseLogFile(string filename)
{
if (!System.IO.File.FileExists(filename))
{
Console.WriteLine ("The file specified does not exist.");
}
else
{
FileStream f = new FileStream(filename, FileMode.Open);
StreamReader stream = new StreamReader(f);
string line;
line = stream.ReadLine(); // Headerzeile
line = stream.ReadLine(); // Versionszeile
line = stream.ReadLine(); // Datumszeile
Regex regexDate= new Regex(@"\:\s(?<date>[^\s]+)\s");
Match match = regexDate.Match(line);
string date = "";
if (match.Length != 0)
date = match.Group("date").ToString();
line = stream.ReadLine(); // Felderzeile
Regex regexLine =
new Regex( // Stellen oder : abgleichen
@"(?<time>(\d|\:)+)\s" +
// Stellen oder . abgleichen
@"(?<ip>(\d|\.)+)\s"
+
// alle Nichtleerzeichen abgleichen
@"(?<method>\S+)\s" +
// alle Nichtleerzeichen abgleichen
@"(?<uri>\S+)\s" +
// alle Nichtleerzeichen abgleichen
@"(?<status>\d+)");
// Zeilen lesen und
// IISLogRow für jede Zeile hinzufügen
while ((line = stream.ReadLine()) != null)
{
//Console.WriteLine(line);
match = regexLine.Match(line);
if (match.Length != 0)
{
Console.WriteLine("date: {0} {1}", date,
match.Group("time"));
Console.WriteLine("IP Address: {0}",
match.Group("ip"));
Console.WriteLine("Method: {0}",
match.Group("method"));
Console.WriteLine("Status: {0}",
match.Group("status"));
Console.WriteLine("URI: {0}\n",
match.Group("uri"));
}
}
f.Close();
}
}
}
Die allgemeine Struktur des Codes sollte Ihnen bekannt
vorkommen. In diesem Beispiel werden zwei reguläre Ausdrücke
verwendet. Die Datumszeichenfolge und der reguläre Ausdruck zu
deren Abgleich lauten:
#Date: 1999-12-31 00:01:22
\:\s(?<date>[^\s]+)\s
Im Code werden reguläre Ausdrücke üblicherweise
unter Verwendung der wörtlichen Zeichenfolgensyntax geschrieben, da die reguläre Ausdruckssyntax auch umgekehrte Schrägstriche verwendet. Reguläre
Ausdrücke können am besten gelesen werden, wenn sie in separate
Elemente untergliedert werden. Das
\:
entspricht dem Doppelpunkt (:). Der
umgekehrte Schrägstrich (\) ist erforderlich, da der
Doppelpunkt eine andere Bedeutung aufweist. Das
\s
entspricht einem einzelnen Leerzeichen (Tabulator
oder Leertaste). Im folgenden Abschnitt
(?<date>[^\s]+)
gibt ?<date> den abzugleichenden
Wert an, der später extrahiert werden soll. Die Gruppe [^\s]
wird als Zeichengruppe bezeichnet; das ^-Zeichen bedeutet
hierbei »keines der folgenden Zeichen«. Diese Gruppe gleicht
demnach alle Nichtleerzeichen ab. Des Weiteren werden mit dem +-Zeichen
die Stellen abgeglichen, die der vorangegangenen Beschreibung (Nichtleerzeichen) entsprechen. Die Klammern legen fest, wie die
extrahierte Zeichenfolge abgeglichen werden soll. Im vorangegangenen
Beispiel wird dieser Teil des Ausdrucks mit 1999-12-31
abgeglichen.
Zum genaueren Abgleich hätte der /d-Bezeichner
verwendet werden können. In diesem Fall lautet der ganze Ausdruck:
\:\s(?<date>\d\d\d\d-\d\d-\d\d)\s
Hiermit wird der einfache reguläre Ausdruck
abgedeckt. Zum Abgleich jeder Protokolldateizeile wird ein komplexerer regulärer Ausdruck verwendet.
Aufgrund der Regelmäßigkeit der Zeile hätte auch Split()
verwendet werden können, aber diese Vorgehensweise wäre nicht
so anschaulich. Die Klauseln des regulären Ausdrucks lauten:
(?<time>(\d|\:)+)\s // Zum Extrahieren
der Uhrzeit Stellen oder : abgleichen
(?<ip>(\d|\.)+)\s // Zum Abrufen der IP-Adresse Stellen
oder . abgleichen
(?<method>\S+)\s // Alle Nichtleerzeichen für Methode
(?<uri>\S+)\s // Alle Nichtleerzeichen für uri
(?<status>\d+) // Alle Stellen für Status
1
Das Programm wird unter Verwendung der .NET-Zwischensprache
geschrieben – der gleichen Sprache, mit der C# die Ausgabe einer
Kompilierung erzeugt.
|