5. Häufig verwendete Klassen der Plattform .NET
Wir stellen hier einige häufig verwendete Klassen der Plattform .NET vor. Zuvor zeigen wir, wie man Informationen zu den mehreren hundert verfügbaren Klassen abrufen kann. Diese Hilfe ist selbst für erfahrene C#-Entwickler unverzichtbar. Die Qualität einer Hilfe (einfacher Zugriff, übersichtliche Gliederung, Relevanz der Informationen usw.) kann über Erfolg oder Misserfolg einer Entwicklungsumgebung entscheiden.
5.1. Hilfe zu den .NET-Klassen suchen
Hier geben wir einige Hinweise, wie Sie mit Visual Studio.NET Hilfe finden können
5.1.1. Hilfe/Inhalt
![]() |
- in [1] wählen Sie die Option Help/Contents aus dem Menü.
- in [2] wählen Sie die Option „Visual C# Express Edition“
- in [3], der Hilfe-Baum zu C#
- in [4] ist eine weitere nützliche Option „.NET Framework“, die Zugriff auf alle Klassen des Frameworks „.NET“ gewährt.
Werfen wir einen Blick auf die Kapitelüberschriften der C#-Hilfe:
![]() |
- [1]: Ein Überblick über C#
- [2]: Eine Reihe von Beispielen zu bestimmten Aspekten von C#
- [3]: Ein C#-Kurs – könnte das vorliegende Dokument vorteilhaft ersetzen…
![]() |
- [4]: Für einen tieferen Einblick in C#
- [5]: nützlich für C++- oder Java-Entwickler. Hilft, einige Fallstricke zu vermeiden.
- [6]: Wenn Sie nach Beispielen suchen, können Sie hier beginnen.
![]() |
- [7]: Was Sie wissen müssen, um grafische Benutzeroberflächen zu erstellen
- [8]: Für eine bessere Nutzung von IDE Visual Studio Express
![]() |
- [9]: SQL Server Express 2005 ist eine hochwertige Software, die kostenlos zur Verfügung gestellt wird. Wir werden sie in diesem Kurs verwenden.
Die C#-Hilfe ist nur ein Teil dessen, was ein Entwickler benötigt. Der andere Teil ist die Hilfe zu den Hunderten von Klassen des .NET-Frameworks, die ihm die Arbeit erleichtern werden.
![]() |
- [1]: Wählen Sie die Hilfe zum .NET-Framework aus
- [2]: Die Hilfe befindet sich im Zweig „.NET Framework SDK“
- [3]: Der Zweig „.NET Framework Class Library“ enthält alle Klassen „.NET“ nach dem Namespace, zu dem sie gehören
- [4]: Der Namensraum System, der in den Beispielen der vorangegangenen Kapitel am häufigsten verwendet wurde
![]() |
- [5]: im Namensraum System, ein Beispiel, hier die Struktur DateTime
![]() |
- [6]: Hilfe zur Struktur DateTime
5.1.2. Hilfe/Index/Suche
Die von MSDN bereitgestellte Hilfe ist sehr umfangreich, sodass man möglicherweise nicht weiß, wo man suchen soll. In diesem Fall kann man den Hilfeindex verwenden:
![]() |
- in [1] die Option [Help/Index] verwenden, falls das Hilfefenster noch nicht geöffnet ist; andernfalls [2] in einem bereits geöffneten Hilfefenster verwenden.
- in [3], geben Sie den Bereich an, in dem die Suche erfolgen soll
- In [4] geben Sie an, wonach Sie suchen, in diesem Fall eine Klasse
- in [5] die Antwort
Eine weitere Möglichkeit, Hilfe zu suchen, ist die Verwendung der Suchfunktion der Hilfe:
![]() |
- in [1], verwenden Sie die Option [Help/Search], wenn das Hilfefenster noch nicht geöffnet ist, andernfalls verwenden Sie [2] in einem bereits geöffneten Hilfefenster.
- in [3], geben Sie an, wonach gesucht wird
- in [4] die Suchbereiche filtern
![]() |
- in [5]: Die Antwort in Form verschiedener Themen, in denen der gesuchte Text gefunden wurde.
5.2. Zeichenfolgen
5.2.1. Die Klasse System.String
![]() | ![]() | ![]() |
Die Klasse System.String entspricht dem einfachen String-Typ. Sie verfügt über zahlreiche Eigenschaften und Methoden. Hier sind einige davon:
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
Ein wichtiger Punkt ist zu beachten: Wenn eine Methode eine Zeichenkette zurückgibt, handelt es sich dabei um eine andere Zeichenkette als die, auf die die Methode angewendet wurde. So gibt S1.Trim() die Zeichenkette S2 zurück, und S1 sowie S2 sind zwei verschiedene Zeichenketten.
Eine Zeichenkette C kann als Zeichenarray betrachtet werden. Somit
- ist C[i] das Zeichen i in C
- ist „C.Length“ die Anzahl der Zeichen in C
Betrachten wir das folgende Beispiel:
using System;
namespace Chap3 {
class Program {
static void Main(string[] args) {
string uneChaine = "l'oiseau vole au-dessus des nuages";
affiche("uneChaine=" + uneChaine);
affiche("uneChaine.Length=" + uneChaine.Length);
affiche("chaine[10]=" + uneChaine[10]);
affiche("uneChaine.IndexOf(\"vole\")=" + uneChaine.IndexOf("vole"));
affiche("uneChaine.IndexOf(\"x\")=" + uneChaine.IndexOf("x"));
affiche("uneChaine.LastIndexOf('a')=" + uneChaine.LastIndexOf('a'));
affiche("uneChaine.LastIndexOf('x')=" + uneChaine.LastIndexOf('x'));
affiche("uneChaine.Substring(4,7)=" + uneChaine.Substring(4, 7));
affiche("uneChaine.ToUpper()=" + uneChaine.ToUpper());
affiche("uneChaine.ToLower()=" + uneChaine.ToLower());
affiche("uneChaine.Replace('a','A')=" + uneChaine.Replace('a', 'A'));
string[] champs = uneChaine.Split(null);
for (int i = 0; i < champs.Length; i++) {
affiche("champs[" + i + "]=[" + champs[i] + "]");
}//for
affiche("Join(\":\",champs)=" + System.String.Join(":", champs));
affiche("(\" abc \").Trim()=[" + " abc ".Trim() + "]");
}//Hauptprogramm
public static void affiche(string msg) {
// zeigt Meldung an
Console.WriteLine(msg);
}//zeigt an
}//Klasse
}//Namensraum
Die Ausführung liefert folgende Ergebnisse:
Betrachten wir ein weiteres Beispiel:
using System;
namespace Chap3 {
class Program {
static void Main(string[] args) {
// die zu analysierende Zeile
string ligne = "un:deux::trois:";
// die Feldtrennzeichen
char[] séparateurs = new char[] { ':' };
// Aufteilung
string[] champs = ligne.Split(séparateurs);
for (int i = 0; i < champs.Length; i++) {
Console.WriteLine("Champs[" + i + "]=" + champs[i]);
}
// Join
Console.WriteLine("join=[" + System.String.Join(":", champs) + "]");
}
}
}
und die Ausführungsergebnisse:
Die Methode „Split“ der Klasse String ermöglicht es, Elemente einer Zeichenkette in ein Array zu übertragen. Die Definition der hier verwendeten Methode „Split“ lautet wie folgt:
public string[] Split(char[] separator);
Zeichentabelle. Diese Zeichen stehen für die Zeichen, die zur Trennung der Felder in der Zeichenkette verwendet werden. Wenn die Zeichenkette also „champ1, champ2, champ3"“ lautet, kann man „separator=new char[] {','}“ verwenden. Wenn das Trennzeichen eine Folge von Leerzeichen ist, wird „separator=null“ verwendet. | |
Array von Zeichenketten, wobei jedes Element des Arrays ein Feld der Zeichenkette ist. |
Die Methode „Join“ ist eine statische Methode der Klasse String:
public static string Join(string separator, string[] value);
Zeichenfolgen-Array | |
eine Zeichenkette, die als Feldtrennzeichen dient | |
eine Zeichenkette, die aus der Verkettung der Elemente des Arrays value besteht, getrennt durch die Zeichenkette separator. |
5.2.2. Die Klasse System.Text.StringBuilder
![]() | ![]() | ![]() |
Zuvor haben wir erwähnt, dass die Methoden der Klasse „String“, die auf eine Zeichenkette S1 angewendet wurden, eine andere Zeichenkette S2 zurückgaben. Die Klasse System.Text.StringBuilder ermöglicht die Bearbeitung von S1, ohne dass eine Zeichenkette S2 erstellt werden muss. Dies verbessert die Leistung, da die Vervielfachung von Zeichenketten mit sehr kurzer Lebensdauer vermieden wird.
Die Klasse unterstützt verschiedene Konstruktoren:
| |
|
Ein Objekt vom Typ StringBuilder arbeitet mit Blöcken von capacité Zeichen, um die zugrunde liegende Zeichenkette zu speichern. Standardmäßig ist capacité auf 16 gesetzt. Mit dem dritten Konstruktor oben lässt sich die Kapazität der Blöcke festlegen. Die Anzahl der capacité-Zeichenblöcke, die zum Speichern einer Zeichenkette S erforderlich sind, wird von der Klasse StringBuilder automatisch angepasst. Es gibt Konstruktoren, um die maximale Zeichenanzahl in einem StringBuilder-Objekt festzulegen. Standardmäßig beträgt diese maximale Kapazität 2 147 483 647.
Hier ein Beispiel zur Veranschaulichung dieses Konzepts von capacité:
using System.Text;
using System;
namespace Chap3 {
class Program {
static void Main(string[] args) {
// str
StringBuilder str = new StringBuilder("test");
Console.WriteLine("taille={0}, capacité={1}", str.Length, str.Capacity);
for (int i = 0; i < 10; i++) {
str.Append("test");
Console.WriteLine("taille={0}, capacité={1}", str.Length, str.Capacity);
}
// str2
StringBuilder str2 = new StringBuilder("test",10);
Console.WriteLine("taille={0}, capacité={1}", str2.Length, str2.Capacity);
for (int i = 0; i < 10; i++) {
str2.Append("test");
Console.WriteLine("taille={0}, capacité={1}", str2.Length, str2.Capacity);
}
}
}
}
- Zeile 7: Erstellung eines Objekts StringBuilder mit einer Blockgröße von 16 Zeichen
- Zeile 8: str.Length ist die aktuelle Zeichenanzahl der Zeichenkette str. str.Capacity ist die Anzahl der Zeichen, die die aktuelle Zeichenkette str speichern kann, bevor ein neuer Block zugewiesen wird.
- Zeile 10: str.Append(String S) ermöglicht es, die Zeichenkette S vom Typ String an die Zeichenkette str vom Typ StringBuilder anzuhängen.
- Zeile 14: Erstellung eines Objekts vom Typ StringBuilder mit einer Blockkapazität von 10 Zeichen
Das Ergebnis der Ausführung:
Diese Ergebnisse zeigen, dass die Klasse einem eigenen Algorithmus folgt, um neue Blöcke zuzuweisen, wenn ihre Kapazität nicht ausreicht:
- Zeilen 4–5: Erhöhung der Kapazität um 16 Zeichen
- Zeilen 8–9: Erhöhung der Kapazität um 32 Zeichen, obwohl 16 ausgereicht hätten.
Hier sind einige der Methoden der Klasse:
| |
| |
| |
| |
|
Hier ein Beispiel:
using System.Text;
using System;
namespace Chap3 {
class Program {
static void Main(string[] args) {
// str3
StringBuilder str3 = new StringBuilder("test");
Console.WriteLine(str3.Append("abCD").Insert(2, "xyZT").Remove(0, 2).Replace("xy", "XY"));
}
}
}
und die Ergebnisse:
5.3. Arrays
Arrays leiten sich von der Klasse „Array“ ab:
![]() | ![]() | ![]() |
Die Klasse „Array“ verfügt über verschiedene Methoden zum Sortieren eines Arrays, zum Suchen eines Elements in einem Array, zum Ändern der Größe eines Arrays usw. Wir stellen einige Eigenschaften und Methoden dieser Klasse vor. Sie sind fast alle überladen, c.a.d, sodass sie in verschiedenen Varianten existieren. Jedes Array erbt diese Eigenschaften und Methoden.
Eigenschaften
Methoden
Das folgende Programm veranschaulicht die Verwendung einiger Methoden der Klasse Array:
using System;
namespace Chap3 {
class Program {
// Suchtyp
enum TypeRecherche { linéaire, dichotomique };
// Hauptmethode
static void Main(string[] args) {
// Einlesen der per Tastatur eingegebenen Array-Elemente
double[] éléments;
Saisie(out éléments);
// Anzeige eines unsortierten Arrays
Affiche("Tableau non trié", éléments);
// Lineare Suche im unsortierten Array
Recherche(éléments, TypeRecherche.linéaire);
// Sortieren des Arrays
Array.Sort(éléments);
// Anzeige des sortierten Arrays
Affiche("Tableau trié", éléments);
// Dichotomische Suche in der sortierten Tabelle
Recherche(éléments, TypeRecherche.dichotomique);
}
// Eingabe der Werte für die Array-Elemente
// Elemente: Verweis auf das mit der Methode erstellte Array
static void Saisie(out double[] éléments) {
bool terminé = false;
string réponse;
bool erreur;
double élément = 0;
int i = 0;
// Zu Beginn existiert das Array nicht
éléments = null;
// Schleife zur Eingabe der Array-Elemente
while (!terminé) {
// Frage
Console.Write("Elément (réel) " + i + " du tableau (rien pour terminer) : ");
// Antwort auslesen
réponse = Console.ReadLine().Trim();
// Ende der Eingabe, wenn die Zeichenkette leer ist
if (réponse.Equals(""))
break;
// Überprüfung der Eingabe
try {
élément = Double.Parse(réponse);
erreur = false;
} catch {
Console.Error.WriteLine("Saisie incorrecte, recommencez");
erreur = true;
}//Try-Catch
// wenn kein Fehler
if (!erreur) {
// ein weiteres Element im Array
i += 1;
// Array-Größe anpassen, um das neue Element aufzunehmen
Array.Resize(ref éléments, i);
// Einfügen des neuen Elements
éléments[i - 1] = élément;
}
}//while
}
// generische Methode zum Anzeigen der Elemente eines Arrays
static void Affiche<T>(string texte, T[] éléments) {
Console.WriteLine(texte.PadRight(50, '-'));
foreach (T élément in éléments) {
Console.WriteLine(élément);
}
}
// Suche nach einem Element im Array
// Elemente: Array von reellen Zahlen
// TypeRecherche: dichotom oder linear
static void Recherche(double[] éléments, TypeRecherche type) {
// Suche
bool terminé = false;
string réponse = null;
double élément = 0;
bool erreur = false;
int i = 0;
while (!terminé) {
// Frage
Console.WriteLine("Elément cherché (rien pour arrêter) : ");
// Antwort lesen und überprüfen
réponse = Console.ReadLine().Trim();
// Fertig?
if (réponse.Equals(""))
break;
// Überprüfung
try {
élément = Double.Parse(réponse);
erreur = false;
} catch {
Console.WriteLine("Erreur, recommencez...");
erreur = true;
}//try-catch
// wenn kein Fehler
if (!erreur) {
// Suche nach dem Element im Array
if (type == TypeRecherche.dichotomique)
// binäre Suche
i = Array.BinarySearch(éléments, élément);
else
// lineare Suche
i = Array.IndexOf(éléments, élément);
// Anzeige der Antwort
if (i >= 0)
Console.WriteLine("Trouvé en position " + i);
else
Console.WriteLine("Pas dans le tableau");
}//if
}//while
}
}
}
- Zeilen 27–62: Die Methode Saisie erfasst die per Tastatur eingegebenen Elemente eines Arrays éléments. Da das Array nicht im Voraus dimensioniert werden kann (seine endgültige Größe ist nicht bekannt), muss es bei jedem neuen Element neu dimensioniert werden (Zeile 57). Ein effizienterer Algorithmus wäre gewesen, dem Array Speicherplatz in Gruppen von N Elementen zuzuweisen. Ein Array ist jedoch nicht dafür ausgelegt, in seiner Größe angepasst zu werden. Dieser Fall lässt sich besser mit einer Liste (ArrayList, List<T>) lösen.
- Zeilen 75–113: Mit der Methode Recherche kann im Array éléments nach einem über die Tastatur eingegebenen Element gesucht werden. Die Suchmethode unterscheidet sich je nachdem, ob das Array sortiert ist oder nicht. Bei einem unsortierten Array wird eine lineare Suche mit der Methode IndexOf in Zeile 106 durchgeführt. Bei einem sortierten Array wird eine dichotomische Suche mit der Methode BinarySearch in Zeile 103 durchgeführt.
- Zeile 18: Das Array éléments wird sortiert. Hier wird eine Variante von Sort verwendet, die nur einen Parameter hat: das zu sortierende Array. Die zum Vergleich der Array-Elemente verwendete Reihenfolge ist dann die implizite Reihenfolge dieser Elemente. In diesem Fall sind die Elemente numerisch. Es wird die natürliche Reihenfolge der Zahlen verwendet.
Die Bildschirmausgabe sieht wie folgt aus:
5.4. Generische Sammlungen
Neben dem Array gibt es verschiedene Klassen zum Speichern von Sammlungen von Elementen. Es gibt generische Versionen im Namensraum System.Collections.Generic und nicht-generische Versionen in System.Collections. Wir stellen zwei häufig verwendete generische Sammlungen vor: die Liste und das Wörterbuch.
Die Liste der generischen Sammlungen lautet wie folgt:

5.4.1. Die generische Klasse List<T>
Die Klasse System.Collections.Generic.List<T> ermöglicht die Implementierung von Sammlungen von Objekten vom Typ T, deren Größe sich während der Programmausführung ändert. Ein Objekt vom Typ List<T> lässt sich fast wie ein Array handhaben. So wird das Element i einer Liste l als l[i] bezeichnet.
Es gibt außerdem einen nicht-generischen Listentyp: ArrayList, der Referenzen auf beliebige Objekte speichern kann. ArrayList ist funktional gleichbedeutend mit List<Object>.. Ein Objekt vom Typ ArrayList sieht wie folgt aus:
![]() |
Im obigen Beispiel verweisen die Elemente 0, 1 und i der Liste auf Objekte unterschiedlicher Typen. Ein Objekt muss zunächst angelegt werden, bevor seine Referenz zur Liste ArrayList hinzugefügt werden kann. Obwohl ein ArrayList Objektreferenzen speichert, ist es möglich, darin Zahlen zu speichern. Dies geschieht über einen Mechanismus namens Boxing: Die Zahl wird in ein Objekt O vom Typ Object gekapselt, und es ist die Referenz O, die in der Liste gespeichert wird. Dieser Mechanismus ist für den Entwickler transparent. Man kann somit schreiben:
Dies führt zu folgendem Ergebnis:
![]() |
Im obigen Beispiel wurde die Zahl 4 in ein O-Objekt gekapselt und die O-Referenz in der Liste gespeichert. Um sie abzurufen, kann man schreiben:
int i = (int)liste[0];
Die Operation „Object -> int“ wird als Unboxing bezeichnet. Wenn eine Liste vollständig aus Typen int besteht, verbessert die Deklaration als List<int> die Leistung. Tatsächlich werden die Zahlen vom Typ int dann in der Liste selbst gespeichert und nicht in Typen Object außerhalb der Liste. Es finden keine Boxing-/Unboxing-Operationen mehr statt.
Wenn es sich bei einem Objekt vom Typ List<T> oder T um eine Klasse handelt, speichert die Liste auch hier die Referenzen der Objekte vom Typ T:
![]() |
Hier sind einige der Eigenschaften und Methoden generischer Listen:
Eigenschaften
Methoden
Nehmen wir das zuvor behandelte Beispiel mit einem Objekt vom Typ Array wieder auf und bearbeiten wir es nun mit einem Objekt vom Typ List<T>.. Da die Liste ein Objekt ist, das einem Array ähnelt, ändert sich der Code nur geringfügig. Wir stellen hier nur die wesentlichen Änderungen vor:
using System;
using System.Collections.Generic;
namespace Chap3 {
class Program {
// Suchart
enum TypeRecherche { linéaire, dichotomique };
// Hauptmethode
static void Main(string[] args) {
// Einlesen der per Tastatur eingegebenen Listenelemente
List<double> éléments;
Saisie(out éléments);
// Anzahl der Elemente
Console.WriteLine("La liste a {0} éléments et une capacité de {1} éléments", éléments.Count, éléments.Capacity);
// Anzeige der unsortierten Liste
Affiche("Liste non triée", éléments);
// Lineare Suche in der unsortierten Liste
Recherche(éléments, TypeRecherche.linéaire);
// Sortierung der Liste
éléments.Sort();
// Anzeige der sortierten Liste
Affiche("Liste triée", éléments);
// Dichotomische Suche in der sortierten Liste
Recherche(éléments, TypeRecherche.dichotomique);
}
// Eingabe der Werte aus der Elementliste
// Elemente: Verweis auf die durch die Methode erstellte Liste
static void Saisie(out List<double> éléments) {
...
// Zu Beginn ist die Liste leer
éléments = new List<double>();
// Schleife zur Eingabe der Listenelemente
while (!terminé) {
...
// wenn kein Fehler vorliegt
if (!erreur) {
// ein weiteres Element in die Liste
éléments.Add(élément);
}
}//while
}
// Generische Methode zum Anzeigen der Elemente eines durchzählbaren Objekts
static void Affiche<T>(string texte, IEnumerable<T> éléments) {
Console.WriteLine(texte.PadRight(50, '-'));
foreach (T élément in éléments) {
Console.WriteLine(élément);
}
}
// Suche nach einem Element in der Liste
// Elemente: Liste von reellen Zahlen
// TypeRecherche: dichotom oder linear
static void Recherche(List<double> éléments, TypeRecherche type) {
...
while (!terminé) {
...
// wenn kein Fehler vorliegt
if (!erreur) {
// wird das Element in der Liste gesucht
if (type == TypeRecherche.dichotomique)
// dichotomische Suche
i = éléments.BinarySearch(élément);
else
// lineare Suche
i = éléments.IndexOf(élément);
// Anzeige der Antwort
...
}//if
}//while
}
}
}
- Zeilen 46–51: Die generische Methode Affiche<T> akzeptiert zwei Parameter:
- Der erste Parameter ist ein auszugebender Text
- Der zweite Parameter ist ein Objekt, das die generische Schnittstelle IEnumerable<T> implementiert:
Die foreach-Struktur (T Element in Elemente) in Zeile 48 gilt für jedes Objekt éléments, das die Schnittstelle IEnumerable implementiert. Arrays (Array) und Listen (List<T>) implementieren die Schnittstelle IEnumerable<T>. Daher eignet sich die Methode Affiche sowohl zur Anzeige von Tabellen als auch von Listen.
Die Ausführungsergebnisse des Programms sind dieselben wie im Beispiel mit der Klasse Array.
5.4.2. Die Klasse Dictionary<TKey,TValue>
Die Klasse System.Collections.Generic.Dictionary<TKey,TValue> ermöglicht die Implementierung eines Wörterbuchs. Ein Wörterbuch kann man sich als zweispaltiges Array vorstellen:
Schlüssel | Wert |
Schlüssel1 | Wert1 |
Schlüssel2 | Wert2 |
.. | ... |
In der Klasse *Dictionary<TKey,TValue> sind die Schlüssel vom Typ *Tkey, die Werte vom Typ *TValue*. Die Schlüssel sind eindeutig, c.a.d, sodass es keine zwei identischen Schlüssel geben kann. Ein solches Wörterbuch könnte wie folgt aussehen, wenn die Typen TKey und TValue Klassen bezeichnen würden:
![]() |
Der mit dem Schlüssel C eines Wörterbuchs D verknüpfte Wert wird durch die Notation D[C] ausgedrückt. Dieser Wert ist les- und schreibbar. Somit kann man schreiben:
Wenn der Schlüssel c im Wörterbuch D nicht vorhanden ist, löst die Notation D[c] eine Ausnahme aus.
Die wichtigsten Methoden und Eigenschaften der Klasse ***Dictionary<TKey,TValue>*** sind folgende:
Konstruktoren
Eigenschaften
Methoden
Betrachten wir das folgende Beispielprogramm:
using System;
using System.Collections.Generic;
namespace Chap3 {
class Program {
static void Main(string[] args) {
// Erstellung eines Wörterbuchs <string,int>
string[] liste = { "jean:20", "paul:18", "mélanie:10", "violette:15" };
string[] champs = null;
char[] séparateurs = new char[] { ':' };
Dictionary<string,int> dico = new Dictionary<string,int>();
for (int i = 0; i <liste.Length; i++) {
champs = liste[i].Split(séparateurs);
dico[champs[0]]= int.Parse(champs[1]);
}//for
// Anzahl der Elemente im Wörterbuch
Console.WriteLine("Le dictionnaire a " + dico.Count + " éléments");
// Liste der Schlüssel
Affiche("[Liste des clés]",dico.Keys);
// Liste der Werte
Affiche("[Liste des valeurs]", dico.Values);
// Liste der Schlüssel und Werte
Console.WriteLine("[Liste des clés & valeurs]");
foreach (string clé in dico.Keys) {
Console.WriteLine("clé=" + clé + " valeur=" + dico[clé]);
}
// Der Schlüssel „paul“ wird gelöscht
Console.WriteLine("[Suppression d'une clé]");
dico.Remove("paul");
// Liste der Schlüssel und Werte
Console.WriteLine("[Liste des clés & valeurs]");
foreach (string clé in dico.Keys) {
Console.WriteLine("clé=" + clé + " valeur=" + dico[clé]);
}
// Suche im Wörterbuch
String nomCherché = null;
Console.Write("Nom recherché (rien pour arrêter) : ");
nomCherché = Console.ReadLine().Trim();
int value;
while (!nomCherché.Equals("")) {
dico.TryGetValue(nomCherché, out value);
if (value!=0) {
Console.WriteLine(nomCherché + "," + value);
} else {
Console.WriteLine("Nom " + nomCherché + " inconnu");
}
// nächste Suche
Console.Out.Write("Nom recherché (rien pour arrêter) : ");
nomCherché = Console.ReadLine().Trim();
}//while
}
// Generische Methode zum Anzeigen der Elemente eines aufzählbaren Typs
static void Affiche<T>(string texte, IEnumerable<T> éléments) {
Console.WriteLine(texte.PadRight(50, '-'));
foreach (T élément in éléments) {
Console.WriteLine(élément);
}
}
}
}
- Zeile 8: ein Array von string, das zur Initialisierung des Dictionaries <string,int> dient
- Zeile 11: das Wörterbuch <string,int>
- Zeilen 12–15: seine Initialisierung anhand des Arrays von string aus Zeile 8
- Zeile 17: Anzahl der Einträge im Wörterbuch
- Zeile 19: die Schlüssel des Wörterbuchs
- Zeile 21: die Werte des Wörterbuchs
- Zeile 29: Löschen eines Eintrags aus dem Wörterbuch
- Zeile 41: Suche nach einem Schlüssel im Wörterbuch. Falls dieser nicht vorhanden ist, setzt die Methode TryGetValue den Wert 0 in value, da value vom Typ „numerisch“ ist. Diese Technik ist hier nur anwendbar, weil bekannt ist, dass der Wert 0 nicht im Wörterbuch enthalten ist.
Die Ausführungsergebnisse lauten wie folgt:
5.5. Die Textdateien
5.5.1. Die Klasse StreamReader
Die Klasse System.IO.StreamReader ermöglicht das Einlesen des Inhalts einer Textdatei. Sie ist sogar in der Lage, Datenströme zu verarbeiten, die keine Dateien sind. Hier sind einige ihrer Eigenschaften und Methoden:
Konstruktoren
Eigenschaften
Methoden
Hier ein Beispiel:
using System;
using System.IO;
namespace Chap3 {
class Program {
static void Main(string[] args) {
// Ausführungsverzeichnis
Console.WriteLine("Répertoire d'exécution : "+Environment.CurrentDirectory);
string ligne = null;
StreamReader fluxInfos = null;
// Inhalt der Datei infos.txt lesen
try {
// Lesen 1
Console.WriteLine("Lecture 1----------------");
using (fluxInfos = new StreamReader("infos.txt")) {
ligne = fluxInfos.ReadLine();
while (ligne != null) {
Console.WriteLine(ligne);
ligne = fluxInfos.ReadLine();
}
}
// Lesen 2
Console.WriteLine("Lecture 2----------------");
using (fluxInfos = new StreamReader("infos.txt")) {
Console.WriteLine(fluxInfos.ReadToEnd());
}
} catch (Exception e) {
Console.WriteLine("L'erreur suivante s'est produite : " + e.Message);
}
}
}
}
- Zeile 8: Zeigt den Namen des Ausführungsverzeichnisses an
- Zeilen 12, 27: Ein try/catch-Block zur Behandlung einer möglichen Ausnahme.
- Zeile 15: Die Struktur „using flux=new StreamReader(...)“ dient dazu, den Datenstrom nach seiner Verwendung nicht explizit schließen zu müssen. Das Schließen erfolgt automatisch, sobald der Geltungsbereich von using verlassen wird.
- Zeile 15: Die gelesene Datei heißt infos.txt. Da es sich um einen relativen Pfadnamen handelt, wird sie in dem in Zeile 8 angezeigten Ausführungsverzeichnis gesucht. Ist sie dort nicht vorhanden, wird eine Ausnahme ausgelöst und durch das try/catch-Block abgefangen.
- Zeilen 16–20: Die Datei wird zeilenweise gelesen
- Zeile 25: Die Datei wird in einem Durchgang gelesen
Die Datei „infos.txt“ sieht wie folgt aus:
und befindet sich im folgenden Ordner des C#-Projekts:
![]() |
Wir werden feststellen, dass bin/Release der Ausführungsordner ist, wenn das Projekt mit Strg+F5 ausgeführt wird.
Die Ausführung liefert folgende Ergebnisse:
Wenn man in Zeile 15 den Dateinamen xx.txt eingibt, erhält man folgende Ergebnisse:
5.5.2. Die Klasse StreamWriter
Die Klasse System.IO.StreamReader ermöglicht das Schreiben in eine Textdatei. Wie die Klasse StreamReader ist sie tatsächlich in der Lage, Datenströme zu verarbeiten, die keine Dateien sind. Hier sind einige ihrer Eigenschaften und Methoden:
Konstruktoren
Eigenschaften
Methoden
Betrachten wir das folgende Beispiel:
using System;
using System.IO;
namespace Chap3 {
class Program2 {
static void Main(string[] args) {
// Ausführungsverzeichnis
Console.WriteLine("Répertoire d'exécution : " + Environment.CurrentDirectory);
string ligne = null; // eine Textzeile
StreamWriter fluxInfos = null; // die Textdatei
try {
// Erstellung der Textdatei
using (fluxInfos = new StreamWriter("infos2.txt")) {
Console.WriteLine("Mode AutoFlush : {0}", fluxInfos.AutoFlush);
// Einlesen der über die Tastatur eingegebenen Zeile
Console.Write("ligne (rien pour arrêter) : ");
ligne = Console.ReadLine().Trim();
// Schleife, solange die eingegebene Zeile nicht leer ist
while (ligne != "") {
// Zeile in Textdatei schreiben
fluxInfos.WriteLine(ligne);
// Lesen einer neuen Zeile von der Tastatur
Console.Write("ligne (rien pour arrêter) : ");
ligne = Console.ReadLine().Trim();
}//while
}
} catch (Exception e) {
Console.WriteLine("L'erreur suivante s'est produite : " + e.Message);
}
}
}
}
- Zeile 13: Auch hier verwenden wir die Syntax using(flux), um den Datenstrom nicht explizit mit einer Close-Operation schließen zu müssen. Dieses Schließen erfolgt automatisch beim Verlassen von using.
- Warum ein try/catch in den Zeilen 11 und 27? In Zeile 13 könnten wir einen Dateinamen in der Form /rep1/rep2/ .../fichier mit einem Pfad /rep1/rep2/..., der nicht existiert, wodurch die Erstellung von fichier unmöglich wird. In diesem Fall würde eine Ausnahme ausgelöst werden. Es gibt weitere mögliche Ausnahmefälle (volles Laufwerk, unzureichende Berechtigungen, ...)
Die Ausführungsergebnisse lauten wie folgt:
Die Datei infos2.txt wurde im Ordner bin/Release des Projekts erstellt:
![]() | ![]() |
5.6. Die Binärdateien
Die Klassen System.IO.BinaryReader und System.IO.BinaryWriter dienen zum Lesen und Schreiben von Binärdateien.
Betrachten wir die folgende Anwendung:
// Syntax: pg, Text, Bin, Logs
// Eine Textdatei (text) wird gelesen und ihr Inhalt in einer Binärdatei (bin) gespeichert
// Die Textdatei enthält Zeilen der Form „Name: Alter“, die in einer Struktur vom Typ „string, int“ gespeichert werden
// (logs) ist eine Textdatei mit Protokollen
Die Textdatei hat folgenden Inhalt:
Das Programm lautet wie folgt:
using System;
using System.IO;
// Syntax: pg, Text, bin, logs
// Es wird eine Textdatei (text) gelesen und ihr Inhalt in eine Binärdatei (bin) gespeichert
// Die Textdatei enthält Zeilen der Form „Name: Alter“, die in einer Struktur vom Typ „string, int“ gespeichert werden
// (logs) ist eine Textdatei mit Protokollen
namespace Chap3 {
class Program {
static void Main(string[] arguments) {
// Es sind 3 Argumente erforderlich
if (arguments.Length != 3) {
Console.WriteLine("syntaxe : pg texte binaire log");
Environment.Exit(1);
}//if
// Variablen
string ligne=null;
string nom=null;
int age=0;
int numLigne = 0;
char[] séparateurs = new char[] { ':' };
string[] champs=null;
StreamReader input = null;
BinaryWriter output = null;
StreamWriter logs = null;
bool erreur = false;
// Lesen einer Textdatei – Schreiben einer Binärdatei
try {
// Öffnen der Textdatei zum Lesen
input = new StreamReader(arguments[0]);
// Öffnen der Binärdatei zum Schreiben
output = new BinaryWriter(new FileStream(arguments[1], FileMode.Create, FileAccess.Write));
// Logdatei zum Schreiben öffnen
logs = new StreamWriter(arguments[2]);
// Auswertung der Textdatei
while ((ligne = input.ReadLine()) != null) {
// eine weitere Zeile
numLigne++;
// Leere Zeile?
if (ligne.Trim() == "") {
// wird ignoriert
continue;
}
// eine Zeile mit Name: Alter
champs = ligne.Split(séparateurs);
// Wir benötigen 2 Felder
if (champs.Length != 2) {
// Der Fehler wird protokolliert
logs.WriteLine("La ligne n° [{0}] du fichier [{1}] a un nombre de champs incorrect", numLigne, arguments[0]);
// nächste Zeile
continue;
}//if
// Das erste Feld darf nicht leer sein
erreur = false;
nom = champs[0].Trim();
if (nom == "") {
// Der Fehler wird protokolliert
logs.WriteLine("La ligne n° [{0}] du fichier [{1}] a un nom vide", numLigne, arguments[0]);
erreur = true;
}
// Das zweite Feld muss eine ganze Zahl >= 0 sein
if (!int.TryParse(champs[1],out age) || age<0) {
// Der Fehler wird protokolliert
logs.WriteLine("La ligne n° [{0}] du fichier [{1}] a un âge [{2}] incorrect", numLigne, arguments[0], champs[1].Trim());
erreur = true;
}//if
// Wenn kein Fehler vorliegt, werden die Daten in die Binärdatei geschrieben
if (!erreur) {
output.Write(nom);
output.Write(age);
}
// nächste Zeile
}//while
}catch(Exception e){
Console.WriteLine("L'erreur suivante s'est produite : {0}", e.Message);
} finally {
// Dateien schließen
if(input!=null) input.Close();
if(output!=null) output.Close();
if(logs!=null) logs.Close();
}
}
}
}
Betrachten wir nun die Vorgänge bezüglich der Klasse BinaryWriter:
- Zeile 34: Das Objekt BinaryWriter wird durch den Vorgang geöffnet
output=new BinaryWriter(new FileStream(arguments[1],FileMode.Create,FileAccess.Write));
Das Argument des Konstruktors muss ein Stream sein. Hier handelt es sich um einen Stream, der aus einer Datei (FileStream) erstellt wurde, für die Folgendes angegeben wird:
- (Fortsetzung)
- den Namen
- die auszuführende Operation, hier FileMode.Create zum Erstellen der Datei
- die Zugriffsart, hier FileAccess.Write für einen Schreibzugriff auf die Datei
- Zeilen 70–73: die Schreibvorgänge
Die Klasse BinaryWriter verfügt über verschiedene überladene Methoden Write zum Schreiben der verschiedenen einfachen Datentypen
- Zeile 81: die Operation zum Schließen des Datenstroms
Die drei Argumente der Methode Main werden dem Projekt (über dessen Eigenschaften) [1] übergeben, und die zu verarbeitende Textdatei wird im Ordner bin/Release abgelegt. [2]:
![]() |
Mit der folgenden Datei [personnes1.txt]:
lauten die Ergebnisse der Ausführung wie folgt:
![]() |
- in [1], die so erstellte Binärdatei [personnes1.bin] sowie die Protokolldatei [logs.txt]. Diese hat folgenden Inhalt:
Der Inhalt der Binärdatei [personnes1.bin] wird uns durch das folgende Programm bereitgestellt. Dieses akzeptiert ebenfalls drei Argumente:
// Syntax: pg bin text logs
// Eine Binärdatei „bin“ wird gelesen und ihr Inhalt in eine Textdatei („text“) gespeichert
// Die Binärdatei hat eine Struktur aus Zeichenketten und Ganzzahlen
// Die Textdatei enthält Zeilen der Form „Name: Alter“
// „logs“ ist eine Textdatei mit Protokollen
Wir führen also den umgekehrten Vorgang durch. Wir lesen eine Binärdatei ein, um eine Textdatei zu erstellen. Wenn die erzeugte Textdatei mit der Originaldatei identisch ist, wissen wir, dass die Konvertierung von Text → Binär → Text erfolgreich verlaufen ist. Der Code lautet wie folgt:
using System;
using System.IO;
// Syntax: pg, bin, Text, logs
// Eine Binärdatei „bin“ wird gelesen und ihr Inhalt in eine Textdatei („text“) geschrieben
// Die Binärdatei hat die Struktur „string, int“
// Die Textdatei enthält Zeilen der Form „Name: Alter“
// „logs“ ist eine Textdatei mit Protokollen
namespace Chap3 {
class Program2 {
static void Main(string[] arguments) {
// Es sind 3 Argumente erforderlich
if (arguments.Length != 3) {
Console.WriteLine("syntaxe : pg binaire texte log");
Environment.Exit(1);
}//if
// Variablen
string nom = null;
int age = 0;
int numPersonne = 1;
BinaryReader input = null;
StreamWriter output = null;
StreamWriter logs = null;
bool fini;
// Binärdatei lesen – Textdatei schreiben
try {
// Öffnen der Binärdatei zum Lesen
input = new BinaryReader(new FileStream(arguments[0], FileMode.Open, FileAccess.Read));
// Öffnen der Textdatei zum Schreiben
output = new StreamWriter(arguments[1]);
// Logdatei zum Schreiben öffnen
logs = new StreamWriter(arguments[2]);
// Auswertung der Binärdatei
fini = false;
while (!fini) {
try {
// Auslesen des Namens
nom = input.ReadString().Trim();
// Alter auslesen
age = input.ReadInt32();
// Schreiben in Textdatei
output.WriteLine(nom + ":" + age);
// nächste Person
numPersonne++;
} catch (EndOfStreamException) {
fini = true;
} catch (Exception e) {
logs.WriteLine("L'erreur suivante s'est produite à la lecture de la personne n° {0} : {1}", numPersonne, e.Message);
}
}//while
} catch (Exception e) {
Console.WriteLine("L'erreur suivante s'est produite : {0}", e.Message);
} finally {
// Dateien schließen
if (input != null)
input.Close();
if (output != null)
output.Close();
if (logs != null)
logs.Close();
}
}
}
}
Werfen wir einen Blick auf die Vorgänge rund um die Klasse BinaryReader:
- Zeile 30: Das Objekt BinaryReader wird durch den Vorgang
input=new BinaryReader(new FileStream(arguments[0],FileMode.Open,FileAccess.Read));
Das Argument des Konstruktors muss ein Stream sein. Hier handelt es sich um einen Stream, der aus einer Datei (FileStream) erstellt wurde, für die folgende Angaben gemacht werden:
- (Fortsetzung)
- den Namen
- die auszuführende Operation, hier FileMode.Open zum Öffnen einer vorhandenen Datei
- die Zugriffsart, hier FileAccess.Read für den Lesezugriff auf die Datei
- Zeilen 40, 42: die Lesevorgänge
Die Klasse BinaryReader verfügt über verschiedene Methoden ReadXX zum Lesen der verschiedenen einfachen Datentypen
- Zeile 60: Der Vorgang zum Schließen des Datenstroms
Wenn man die beiden Programme nacheinander ausführt und dabei personnes1.txt in personnes1.bin und anschließend personnes1.bin in personnes2.txt2 umwandelt, erhält man folgende Ergebnisse:
![]() |
- in [1] ist das Projekt so konfiguriert, dass die zweite Anwendung ausgeführt wird
- In [2] sind die an Main übergebenen Argumente
- in [3] die durch die Ausführung der Anwendung erzeugten Dateien.
Der Inhalt von [personnes2.txt] lautet wie folgt:
5.7. Reguläre Ausdrücke
Die Klasse System.Text.RegularExpressions.Regex ermöglicht die Verwendung regulärer Ausdrücke. Mit diesen lässt sich das Format einer Zeichenkette prüfen. So kann beispielsweise überprüft werden, ob eine Zeichenkette, die ein Datum darstellt, tatsächlich im Format tt/mm/jj vorliegt. Dazu wird ein Muster verwendet und die Zeichenkette mit diesem Muster verglichen. In diesem Beispiel müssen t, m und j Ziffern sein. Das Muster für ein gültiges Datumsformat lautet dann „\d\d/\d\d/\d\d“, wobei das Symbol \d für eine Ziffer steht. In einem Muster können folgende Symbole verwendet werden:
Beschreibung | |
Markiert das folgende Zeichen als Sonderzeichen oder Literal. Beispielsweise entspricht „n“ dem Zeichen „n“. „\n“ entspricht einem Zeilenumbruchzeichen. Die Zeichenfolge „\\“ entspricht „\“, während „\(" dem Zeichen „(“ entspricht. | |
Entspricht dem Beginn der Eingabe. | |
Entspricht dem Ende der Eingabe. | |
Entspricht dem vorhergehenden Zeichen nullmal oder mehrmals. So entspricht „zo*“ sowohl „z“ als auch „zoo“. | |
Entspricht dem vorhergehenden Zeichen ein- oder mehrmals. So entspricht „zo+“ beispielsweise „zoo“, aber nicht „z“. | |
Entspricht dem vorhergehenden Zeichen null- oder einmal. Beispielsweise entspricht „a?ve?“ dem Zeichen „ve“ in „lever“. | |
Entspricht einem beliebigen einzelnen Zeichen, mit Ausnahme des Zeilenumbruchzeichens. | |
Sucht nach dem modèle und speichert die Übereinstimmung. Die entsprechende Teilzeichenfolge kann mithilfe von „Item [0]...[n]“ aus der erhaltenen Sammlung „Matches“ extrahiert werden. Um Übereinstimmungen mit Zeichen in Klammern ( ) zu finden, verwenden Sie „\(" oder „\)“. | |
Entspricht entweder x oder y. Beispielsweise entspricht „z|foot“ entweder „z“ oder „foot“. „(z|f)oo“ entspricht entweder „zoo“ oder „foo“. | |
n ist eine nicht-negative ganze Zahl. Entspricht genau dem n-fachen des Zeichens. Beispielsweise entspricht „o{2}“ nicht dem „o“ in „Bob“, sondern den ersten beiden „o“ in „fooooot“. | |
n ist eine nicht-negative ganze Zahl. Entspricht mindestens n-mal dem Zeichen. Beispielsweise entspricht „o{2,}“ nicht dem „o“ in „Bob“, sondern allen „o“ in „fooooot“. „o{1,}“ entspricht „o+“ und „o{0,}“ entspricht „o*“. | |
m und n sind nicht-negative ganze Zahlen. Entspricht mindestens n und höchstens m Mal dem Zeichen. Beispielsweise entspricht „o{1,3}“ den ersten drei „o“ in „foooooot“ und „o{0,1}“ entspricht „o?“. | |
Zeichensatz. Entspricht einem der angegebenen Zeichen. Beispielsweise entspricht „[abc]“ dem Zeichen „a“ in „plat“. | |
Negativer Zeichensatz. Entspricht jedem nicht angegebenen Zeichen. Beispielsweise entspricht „[^abc]“ dem Zeichen „p“ in „plat“. | |
Zeichenbereich. Entspricht jedem Zeichen in der angegebenen Zeichenfolge. Beispielsweise entspricht „[a-z]“ jedem Kleinbuchstaben zwischen „a“ und „z“. | |
Negativer Zeichenbereich. Entspricht jedem Zeichen, das nicht in der angegebenen Zeichenfolge enthalten ist. Beispielsweise entspricht „[^m-z]“ jedem Zeichen, das nicht zwischen „m“ und „z“ liegt. | |
Entspricht einer Grenze, die ein Wort darstellt, d. h. der Position zwischen einem Wort und einem Leerzeichen. Beispielsweise entspricht „er\b“ dem Wort „er“ in „lever“, nicht jedoch dem Wort „er“ in „verbe“. | |
Entspricht einer Grenze, die kein Wort darstellt. „en*t\B“ entspricht „ent“ in „bien entendu“. | |
Entspricht einem Zeichen, das eine Ziffer darstellt. Entspricht [0-9]. | |
Entspricht einem Zeichen, das keine Ziffer darstellt. Entspricht [^0-9]. | |
Entspricht einem Seitenumbruchzeichen. | |
Entspricht einem Zeilenumbruchzeichen. | |
Entspricht einem Wagenrücklaufzeichen. | |
Entspricht allen Leerzeichen, einschließlich Leerzeichen, Tabulatoren, Seitenumbrüchen usw. Entspricht „[ \f\n\r\t\v]“. | |
Entspricht jedem nicht-leeren Leerzeichen. Entspricht „[^ \f\n\r\t\v]“. | |
Entspricht einem Tabulatorzeichen. | |
Entspricht einem vertikalen Tabulatorzeichen. | |
Entspricht jedem Zeichen, das ein Wort darstellt und einen Unterstrich enthält. Entspricht „[A-Za-z0-9_]“. | |
Entspricht jedem Zeichen, das kein Wort darstellt. Entspricht „[^A-Za-z0-9_]“. | |
Entspricht num, wobei num eine positive ganze Zahl ist. Bezieht sich auf gespeicherte Zuordnungen. Beispielsweise entspricht „(.)\1“ zwei aufeinanderfolgenden identischen Zeichen. | |
Entspricht n, wobei n ein oktaler Escape-Wert ist. Oktale Escape-Werte müssen aus 1, 2 oder 3 Ziffern bestehen. Beispielsweise entsprechen sowohl „\11“ als auch „\011“ einem Tabulatorzeichen. „\0011“ entspricht „\001“ & „1“. Oktale Escape-Werte dürfen den Wert 256 nicht überschreiten. Sollte dies der Fall sein, werden nur die ersten beiden Ziffern im Ausdruck berücksichtigt. Ermöglicht die Verwendung der Codes ASCII in regulären Ausdrücken. | |
Entspricht n, wobei n ein hexadezimaler Escape-Wert ist. Hexadezimale Escape-Werte müssen zwingend aus zwei Ziffern bestehen. Beispielsweise entspricht „\x41“ dem Zeichen „A“. „\x041“ entspricht „\x04“ und „1“. Ermöglicht die Verwendung der Codes ASCII in regulären Ausdrücken. |
Ein Element in einer Vorlage kann ein- oder mehrmals vorkommen. Betrachten wir einige Beispiele rund um das Symbol \d, das für eine Ziffer steht:
Vorlage | Bedeutung |
\d | eine Ziffer |
\d? | 0 oder 1 Ziffer |
\d* | 0 oder mehr Ziffern |
\d+ | 1 oder mehr Ziffern |
\d{2} | 2 Ziffern |
\d{3,} | mindestens 3 Ziffern |
\d{5,7} | zwischen 5 und 7 Ziffern |
Stellen wir uns nun das Modell vor, mit dem das erwartete Format einer Zeichenkette beschrieben werden kann:
gesuchte Zeichenfolge | Modell |
ein Datum im Format TT/MM/JJ | \d{2}/\d{2}/\d{2} |
eine Uhrzeit im Format hh:mm:ss | \d{2}:\d{2}:\d{2} |
eine vorzeichenlose Ganzzahl | \d+ |
eine möglicherweise leere Folge von Leerzeichen | \s* |
eine vorzeichenlose Ganzzahl, der Leerzeichen vorangestellt oder nachgestellt sein können | \s*\d+\s* |
eine ganze Zahl, die vorzeichenbehaftet sein kann und der Leerzeichen vorangestellt oder nachgestellt sein können | \s*[+|-]?\s*\d+\s* |
eine vorzeichenlose reelle Zahl, der vor oder nach Leerzeichen stehen können | \s*\d+(.\d*)?\s* |
eine reelle Zahl, die vorzeichenbehaftet sein kann und der Leerräume vorangestellt oder nachgestellt sein können | \s*[+|]?\s*\d+(.\d*)?\s* |
eine Zeichenfolge, die das Wort „juste“ enthält | \bjuste\b |
Man kann angeben, an welcher Stelle in der Zeichenkette nach dem Muster gesucht werden soll:
Muster | Bedeutung |
^Muster | Das Muster steht am Anfang der Zeichenfolge |
Muster$ | Das Muster beendet die Zeichenfolge |
^Muster$ | Das Muster beginnt und beendet die Zeichenfolge |
Muster | Das Muster wird überall in der Zeichenkette gesucht, beginnend am Anfang der Zeichenkette. |
gesuchte Zeichenfolge | Muster |
Eine Zeichenfolge, die mit einem Ausrufezeichen endet | !$ |
eine Zeichenfolge, die mit einem Punkt endet | \.$ |
eine Zeichenkette, die mit der Sequenz // beginnt | ^// |
eine Zeichenfolge, die nur aus einem Wort besteht, dem gegebenenfalls Leerzeichen vorangestellt oder nachgestellt sind | ^\s*\w+\s*$ |
eine Zeichenfolge, die aus zwei Wörtern besteht, denen gegebenenfalls Leerzeichen vorangehen oder folgen | ^\s*\w+\s*\w+\s*$ |
eine Zeichenfolge, die das Wort secret enthält | \bsecret\b |
Teilmengen eines Musters können „extrahiert“ werden. So lässt sich nicht nur überprüfen, ob eine Zeichenfolge einem bestimmten Muster entspricht, sondern es können aus dieser Zeichenfolge auch die Elemente extrahiert werden, die den in Klammern gesetzten Teilmengen des Musters entsprechen. Wenn man also eine Zeichenfolge analysiert, die ein Datum im Format TT/MM/JJ enthält, und zusätzlich die Elemente TT, MM und JJ dieses Datums extrahieren möchte, verwendet man das Muster (\d\d)/(\d\d)/(\d\d).
5.7.1. Überprüfen, ob eine Zeichenkette einem bestimmten Muster entspricht
Ein Objekt vom Typ „Regex“ wird wie folgt erstellt:
Sobald der reguläre Ausdruck als Vorlage erstellt ist, kann er mit der Methode IsMatch mit Zeichenfolgen verglichen werden:
Hier ein Beispiel:
using System;
using System.Text.RegularExpressions;
namespace Chap3 {
class Program {
static void Main(string[] args) {
// ein regulärer Ausdruck als Vorlage
string modèle1 = @"^\s*\d+\s*$";
Regex regex1 = new Regex(modèle1);
// Ein Exemplar mit dem Muster vergleichen
string exemplaire1 = " 123 ";
if (regex1.IsMatch(exemplaire1)) {
Console.WriteLine("[{0}] correspond au modèle [{1}]", exemplaire1, modèle1);
} else {
Console.WriteLine("[{0}] ne correspond pas au modèle [{1}]", exemplaire1, modèle1);
}//if
string exemplaire2 = " 123a ";
if (regex1.IsMatch(exemplaire2)) {
Console.WriteLine("[{0}] correspond au modèle [{1}]", exemplaire2, modèle1);
} else {
Console.WriteLine("[{0}] ne correspond pas au modèle [{1}]", exemplaire2, modèle1);
}//if
}
}
}
und die Ausführungsergebnisse:
5.7.2. Alle Vorkommen eines Musters in einer Zeichenkette finden
Mit der Methode Matches lassen sich die Elemente einer Zeichenkette abrufen, die einem Muster entsprechen:
Die Klasse MatchCollection verfügt über die Eigenschaft „Count“, die die Anzahl der Elemente in der Sammlung angibt. Wenn résultats ein Objekt vom Typ MatchCollection ist, dann ist results[i] das i-te Element dieser Sammlung und vom Typ „Match“. Die Klasse Match verfügt über verschiedene Eigenschaften, darunter die folgenden:
- Value: der Wert des Objekts Match,, also ein Element, das dem Muster entspricht
- Index: Die Position, an der das Element in der durchsuchten Zeichenkette gefunden wurde
Betrachten wir das folgende Beispiel:
using System;
using System.Text.RegularExpressions;
namespace Chap3 {
class Program2 {
static void Main(string[] args) {
// mehrere Vorkommen des Musters im Beispiel
string modèle2 = @"\d+";
Regex regex2 = new Regex(modèle2);
string exemplaire3 = " 123 456 789 ";
MatchCollection résultats = regex2.Matches(exemplaire3);
Console.WriteLine("Modèle=[{0}],exemplaire=[{1}]", modèle2, exemplaire3);
Console.WriteLine("Il y a {0} occurrences du modèle dans l'exemplaire ", résultats.Count);
for (int i = 0; i < résultats.Count; i++) {
Console.WriteLine("[{0}] trouvé en position {1}", résultats[i].Value, résultats[i].Index);
}//for
}
}
}
- Zeile 8: Das gesuchte Muster ist eine Folge von Ziffern
- Zeile 10: Die Zeichenkette, in der nach diesem Muster gesucht wird
- Zeile 11: Es werden alle Elemente von „exemplaire3“ abgerufen, die dem Muster „modèle2“ entsprechen
- Zeilen 14–16: Diese werden angezeigt
Die Ergebnisse der Programmausführung lauten wie folgt:
5.7.3. Teile eines Musters abrufen
Teilmengen eines Musters können „extrahiert“ werden. So kann man nicht nur überprüfen, ob eine Zeichenkette einem bestimmten Muster entspricht, sondern auch die Elemente aus dieser Zeichenkette extrahieren, die den in Klammern gesetzten Teilmengen des Musters entsprechen. Wenn man also eine Zeichenfolge analysiert, die ein Datum im Format TT/MM/JJ enthält, und zusätzlich die Elemente TT, MM und JJ dieses Datums extrahieren möchte, verwendet man das Muster (\d\d)/(\d\d)/(\d\d).
Betrachten wir das folgende Beispiel:
using System;
using System.Text.RegularExpressions;
namespace Chap3 {
class Program3 {
static void Main(string[] args) {
// Erfassung von Elementen in der Vorlage
string modèle3 = @"(\d\d):(\d\d):(\d\d)";
Regex regex3 = new Regex(modèle3);
string exemplaire4 = "Il est 18:05:49";
// Vorlagenprüfung
Match résultat = regex3.Match(exemplaire4);
if (résultat.Success) {
// Die Instanz entspricht der Vorlage
Console.WriteLine("L'exemplaire [{0}] correspond au modèle [{1}]",exemplaire4,modèle3);
// Klammergruppen werden angezeigt
for (int i = 0; i < résultat.Groups.Count; i++) {
Console.WriteLine("groupes[{0}]=[{1}] trouvé en position {2}",i, résultat.Groups[i].Value,résultat.Groups[i].Index);
}//for
} else {
// Das Exemplar entspricht nicht der Vorlage
Console.WriteLine("L'exemplaire[{0}] ne correspond pas au modèle [{1}]", exemplaire4, modèle3);
}
}
}
}
Die Ausführung dieses Programms liefert folgende Ergebnisse:
Die Neuerung findet sich in den Zeilen 12–19:
- Zeile 12: Die Zeichenkette exemplaire4 wird mithilfe der Methode Match mit dem Muster regex3 verglichen. Diese Methode gibt ein bereits vorgestelltes Objekt Match zurück. Wir verwenden hier zwei neue Eigenschaften dieser Klasse:
- Success (Zeile 13): gibt an, ob eine Übereinstimmung vorliegt
- Groups (Zeilen 17, 18): Sammlung, wobei
- Groups[0] dem Teil der Zeichenkette entspricht, der dem Muster
- Groups[i] (i >= 1) der Klammergruppe Nr. i entspricht
Wenn résultat vom Typ Match ist, ist résultats.Groups vom Typ GroupCollection und résultats.Groups[i] vom Typ Group. Die Klasse Group verfügt über zwei Eigenschaften, die wir hier verwenden:
- Value (Zeile 18): Der Wert des Objekts Group, das dem Inhalt einer Klammer entspricht
- Index (Zeile 18): die Position, an der das Element in der durchsuchten Zeichenkette gefunden wurde
5.7.4. Ein Übungsprogramm
Es ist manchmal eine echte Herausforderung, den regulären Ausdruck zu finden, mit dem überprüft werden kann, ob eine Zeichenkette tatsächlich einem bestimmten Muster entspricht. Mit dem folgenden Programm können Sie üben. Es fragt nach einem Muster und einer Zeichenkette und gibt an, ob die Zeichenkette dem Muster entspricht oder nicht.
using System;
using System.Text.RegularExpressions;
namespace Chap3 {
class Program4 {
static void Main(string[] args) {
// Daten
string modèle, chaine;
Regex regex = null;
MatchCollection résultats;
// Der Benutzer wird aufgefordert, die Vorlagen und Exemplare anzugeben, die mit diesem verglichen werden sollen
while (true) {
// Die Vorlage wird abgefragt
Console.Write("Tapez le modèle à tester ou rien pour arrêter :");
modèle = Console.In.ReadLine();
// fertig?
if (modèle.Trim() == "")
break;
// Erstellen des regulären Ausdrucks
try {
regex = new Regex(modèle);
} catch (Exception ex) {
Console.WriteLine("Erreur : " + ex.Message);
continue;
}
// Der Benutzer wird aufgefordert, die mit der Vorlage zu vergleichenden Exemplare anzugeben
while (true) {
Console.Write("Tapez la chaîne à comparer au modèle [{0}] ou rien pour arrêter :", modèle);
chaine = Console.ReadLine();
// Fertig?
if (chaine.Trim() == "")
break;
// Der Vergleich wird durchgeführt
résultats = regex.Matches(chaine);
// Erfolgreich?
if (résultats.Count == 0) {
Console.WriteLine("Je n'ai pas trouvé de correspondances");
continue;
}//if
// werden die Elemente angezeigt, die mit dem Modell übereinstimmen
for (int i = 0; i < résultats.Count; i++) {
Console.WriteLine("J'ai trouvé la correspondance [{0}] en position [{1}]", résultats[i].Value, résultats[i].Index);
// Unterelemente
if (résultats[i].Groups.Count != 1) {
for (int j = 1; j < résultats[i].Groups.Count; j++) {
Console.WriteLine("\tsous-élément [{0}] en position [{1}]", résultats[i].Groups[j].Value, résultats[i].Groups[j].Index);
}
}
}
}
}
}
}
}
Hier ein Ausführungsbeispiel:
5.7.5. Die Methode „Split“
Diese Methode haben wir bereits in der Klasse String kennengelernt:
|
Die Methode Split der Klasse Regex ermöglicht es uns, das Trennzeichen anhand einer Vorlage festzulegen:
|
Nehmen wir beispielsweise an, dass eine Textdatei Zeilen der Form Feld1, Feld2, …, Feldn enthält. Die Felder sind durch ein Komma getrennt, dem jedoch vor oder nach dem Komma Leerzeichen folgen können. Die Methode Split der Klasse string ist in diesem Fall nicht geeignet. Die Methode RegEx bietet die Lösung. Wenn ligne die gelesene Zeile ist, können die Felder wie folgt abgerufen werden:
abgerufen werden, wie das folgende Beispiel zeigt:
using System;
using System.Text.RegularExpressions;
namespace Chap3 {
class Program5 {
static void Main(string[] args) {
// eine Zeile
string ligne = "abc , def , ghi";
// eine Vorlage
Regex modèle = new Regex(@"\s*,\s*");
// Aufschlüsselung einer Zeile in Felder
string[] champs = modèle.Split(ligne);
// Anzeige
for (int i = 0; i < champs.Length; i++) {
Console.WriteLine("champs[{0}]=[{1}]", i, champs[i]);
}
}
}
}
Die Ausführungsergebnisse:
5.8. Beispielanwendung – V3
Wir greifen die in den Abschnitten 3.6 (Version 1) und 4.10 (Version 2) behandelte Anwendung wieder auf.
In der zuletzt untersuchten Version erfolgte die Steuerberechnung in der abstrakten Klasse AbstractImpot:
namespace Chap2 {
abstract class AbstractImpot : IImpot {
// die für die Steuerberechnung erforderlichen Steuerklassen
// stammen aus einer externen Quelle
protected TrancheImpot[] tranchesImpot;
// Steuerberechnung
public int calculer(bool marié, int nbEnfants, int salaire) {
// Berechnung der Anzahl der Anteile
decimal nbParts;
if (marié) nbParts = (decimal)nbEnfants / 2 + 2;
else nbParts = (decimal)nbEnfants / 2 + 1;
if (nbEnfants >= 3) nbParts += 0.5M;
// Berechnung des zu versteuernden Einkommens und des Familienquotienten
decimal revenu = 0.72M * salaire;
decimal QF = revenu / nbParts;
// Steuerberechnung
tranchesImpot[tranchesImpot.Length - 1].Limite = QF + 1;
int i = 0;
while (QF > tranchesImpot[i].Limite) i++;
// Ergebnis zurückgeben
return (int)(revenu * tranchesImpot[i].CoeffR - nbParts * tranchesImpot[i].CoeffN);
}//berechnen
}//Klasse
}
Die Methode calculer in Zeile 38 verwendet das Array tranchesImpot aus Zeile 35, das von der Klasse AbstractImpot nicht initialisiert wurde. Deshalb ist sie abstrakt und muss abgeleitet werden, um nutzbar zu sein. Diese Initialisierung erfolgte durch die abgeleitete Klasse HardwiredImpot:
using System;
namespace Chap2 {
class HardwiredImpot : AbstractImpot {
// Für die Steuerberechnung erforderliche Datentabellen
decimal[] limites = { 4962M, 8382M, 14753M, 23888M, 38868M, 47932M, 0M };
decimal[] coeffR = { 0M, 0.068M, 0.191M, 0.283M, 0.374M, 0.426M, 0.481M };
decimal[] coeffN = { 0M, 291.09M, 1322.92M, 2668.39M, 4846.98M, 6883.66M, 9505.54M };
public HardwiredImpot() {
// Erstellung der Steuertabellen
tranchesImpot = new TrancheImpot[limites.Length];
// Ausfüllen
for (int i = 0; i < tranchesImpot.Length; i++) {
tranchesImpot[i] = new TrancheImpot { Limite = limites[i], CoeffR = coeffR[i], CoeffN = coeffN[i] };
}
}
}// Klasse
}// Namensraum
Im obigen Beispiel waren die für die Steuerberechnung erforderlichen Daten fest im Klassencode hinterlegt. In der neuen Version des Beispiels werden sie in einer Textdatei gespeichert:
4962:0:0
8382:0,068:291,09
14753:0,191:1322,92
23888:0,283:2668,39
38868:0,374:4846,98
47932:0,426:6883,66
0:0,481:9505,54
Da die Verarbeitung dieser Datei zu Ausnahmen führen kann, erstellen wir eine spezielle Klasse, um diese zu behandeln:
using System;
namespace Chap3 {
class FileImpotException : Exception {
// Fehlercodes
[Flags]
public enum CodeErreurs { Acces = 1, Ligne = 2, Champ1 = 4, Champ2 = 8, Champ3 = 16 };
// Fehlercode
public CodeErreurs Code { get; set; }
// Konstruktoren
public FileImpotException() {
}
public FileImpotException(string message)
: base(message) {
}
public FileImpotException(string message, Exception e)
: base(message,e) {
}
}
}
- Zeile 4: Die Klasse FileImpotException leitet sich von der Klasse Exception ab. Sie dient dazu, alle Fehler zu speichern, die bei der Verarbeitung der Textdatei mit den Daten auftreten.
- Zeile 7: Eine Aufzählung, die Fehlercodes darstellt:
- Acces: Fehler beim Zugriff auf die Textdatei mit den Daten
- Ligne: Zeile enthält nicht die drei erwarteten Felder
- Champ1: Feld Nr. 1 ist fehlerhaft
- Champ2: Feld Nr. 2 ist fehlerhaft
- Champ3: Feld Nr. 3 ist fehlerhaft
Einige dieser Fehler können kombiniert auftreten (Champ1, Champ2, Champ3). Daher wurde die Aufzählung CodeErreurs mit dem Attribut [Flags] versehen, was bedeutet, dass die verschiedenen Werte der Aufzählung Zweierpotenzen sein müssen. Ein Fehler in den Feldern 1 und 2 führt dann zum Fehlercode Champ1 | Champ2.
- Zeile 10: Die automatische Eigenschaft Code speichert den Fehlercode.
- Zeile 15: Ein Konstruktor, mit dem ein Objekt vom Typ FileImpotException erstellt werden kann, indem ihm eine Fehlermeldung als Parameter übergeben wird.
- Zeile 18: Ein Konstruktor, mit dem ein Objekt vom Typ FileImpotException erstellt werden kann, indem ihm eine Fehlermeldung und die dem Fehler zugrunde liegende Ausnahme als Parameter übergeben werden.
Die Klasse, die das Array tranchesImpot der Klasse AbstractImpot initialisiert, lautet nun wie folgt:
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
namespace Chap3 {
class FileImpot : AbstractImpot {
public FileImpot(string fileName) {
// Daten
List<TrancheImpot> listTranchesImpot = new List<TrancheImpot>();
int numLigne = 1;
// Ausnahme
FileImpotException fe = null;
// Inhalt der Datei fileName zeilenweise lesen
Regex pattern = new Regex(@"s*:\s*");
// zunächst kein Fehler
FileImpotException.CodeErreurs code = 0;
try {
using (StreamReader input = new StreamReader(fileName)) {
while (!input.EndOfStream && code == 0) {
// aktuelle Zeile
string ligne = input.ReadLine().Trim();
// Leerzeilen werden ignoriert
if (ligne == "") continue;
// Zeile in drei Felder zerlegt, getrennt durch:
string[] champsLigne = pattern.Split(ligne);
// Gibt es 3 Felder?
if (champsLigne.Length != 3) {
code = FileImpotException.CodeErreurs.Ligne;
}
// Konvertierungen der 3 Felder
decimal limite = 0, coeffR = 0, coeffN = 0;
if (code == 0) {
if (!Decimal.TryParse(champsLigne[0], out limite)) code = FileImpotException.CodeErreurs.Champ1;
if (!Decimal.TryParse(champsLigne[1], out coeffR)) code |= FileImpotException.CodeErreurs.Champ2;
if (!Decimal.TryParse(champsLigne[2], out coeffN)) code |= FileImpotException.CodeErreurs.Champ3; ;
}
// Fehler?
if (code != 0) {
// Der Fehler wird vermerkt
fe = new FileImpotException(String.Format("Ligne n° {0} incorrecte", numLigne)) { Code = code };
} else {
// Die neue Steuerklasse wird gespeichert
listTranchesImpot.Add(new TrancheImpot() { Limite = limite, CoeffR = coeffR, CoeffN = coeffN });
// nächste Zeile
numLigne++;
}
}
}
// Die Liste listImpot wird in das Array tranchesImpot übertragen
if (code == 0) {
// Die Liste listImpot wird in das Array tranchesImpot übertragen
tranchesImpot = listTranchesImpot.ToArray();
}
} catch (Exception e) {
// Der Fehler wird vermerkt
fe= new FileImpotException(String.Format("Erreur lors de la lecture du fichier {0}", fileName), e) { Code = FileImpotException.CodeErreurs.Acces };
}
// Fehler melden?
if (fe != null) throw fe;
}
}
}
- Zeile 7: Die Klasse FileImpot leitet sich von der Klasse AbstractImpot ab, so wie dies in Version 2 bei der Klasse HardwiredImpot der Fall war.
- Zeile 9: Der Konstruktor der Klasse FileImpot dient dazu, das Feld trancheImpot seiner Basisklasse AbstractImpot zu initialisieren. Als Parameter akzeptiert er den Namen der Textdatei, die die Daten enthält.
- Zeile 11: Das Feld tranchesImpot der Basisklasse AbstractImpot ist ein Array, das mit den Daten aus der als Parameter übergebenen Datei filename gefüllt werden muss. Das Einlesen einer Textdatei erfolgt sequenziell. Die Anzahl der Zeilen ist erst bekannt, nachdem die gesamte Datei gelesen wurde. Daher kann das Array tranchesImpot nicht vorab dimensioniert werden. Die Daten werden vorübergehend in der generischen Liste listTranchesImpot gespeichert.
Zur Erinnerung: Der Typ TrancheImpot ist eine Struktur:
namespace Chap3 {
// eine Steuerklasse
struct TrancheImpot {
public decimal Limite { get; set; }
public decimal CoeffR { get; set; }
public decimal CoeffN { get; set; }
}
}
- Zeile 14: fe vom Typ FileImpotException dient dazu, einen möglichen Fehler bei der Verarbeitung der Textdatei zu kapseln.
- Zeile 16: Der reguläre Ausdruck für das Feldtrennzeichen in einer Zeile champ1:champ2:champ3 der Textdatei. Die Felder werden durch das Zeichen getrennt, dem eine beliebige Anzahl von Leerzeichen vorangeht und folgt.
- Zeile 18: Der Fehlercode im Falle eines Fehlers
- Zeile 20: Verarbeitung der Textdatei mit einem StreamReader
- Zeile 21: Die Schleife wird so lange wiederholt, wie noch eine Zeile zu lesen ist und kein Fehler aufgetreten ist
- Zeile 27: Die gelesene Zeile wird mithilfe des regulären Ausdrucks aus Zeile 16 in Felder aufgeteilt
- Zeilen 29–31: Es wird überprüft, ob die Zeile tatsächlich drei Felder enthält – ein eventueller Fehler wird vermerkt
- Zeilen 33–38: Konvertierung der drei Zeichenfolgen in drei Dezimalzahlen – eventuelle Fehler werden vermerkt
- Zeilen 40–43: Wenn ein Fehler aufgetreten ist, wird eine Ausnahme vom Typ FileImpotException ausgelöst.
- Zeilen 44–47: Wenn kein Fehler aufgetreten ist, wird die nächste Zeile der Textdatei gelesen, nachdem die Daten der aktuellen Zeile gespeichert wurden.
- Zeilen 52–55: Bei Verlassen der Schleife while werden die Daten der generischen Liste listTranchesImpot in das Array tranchesImpot der Basisklasse AbstractImpot kopiert. Es sei daran erinnert, dass dies die Absicht des Erstellers war.
- Zeilen 56–59: Behandlung einer möglichen Ausnahme. Diese wird in ein Objekt vom Typ FileImpotException gekapselt.
- Zeile 61: Wenn die Ausnahme fe aus Zeile 18 initialisiert wurde, wird sie ausgelöst.
Das gesamte C#-Projekt sieht wie folgt aus:
![]() |
- in [1]: das gesamte Projekt
- in [2,3]: die Eigenschaften der Datei [DataImpot.txt] [2]. Die Eigenschaft [Copy to Output Directory] [3] wird auf „always“ gesetzt. Dadurch wird die Datei [DataImpot.txt] bei jeder Ausführung in den Ordner bin/Release (Release-Modus) oder bin/Debug (Debug-Modus) kopiert. Dort wird sie von der ausführbaren Datei gesucht.
- Bei [4]: Das Gleiche gilt für die Datei [DataImpotInvalide.txt].
Der Inhalt von [DataImpot.txt] lautet wie folgt:
4962:0:0
8382:0,068:291,09
14753:0,191:1322,92
23888:0,283:2668,39
38868:0,374:4846,98
47932:0,426:6883,66
0:0,481:9505,54
Der Inhalt von [DataImpotInvalide.txt] lautet wie folgt:
Das Testprogramm [Program.cs] hat sich nicht geändert: Es handelt sich um das Programm aus Version 2, Abschnitt 4.10, mit folgendem Unterschied:
using System;
namespace Chap3 {
class Program {
static void Main() {
...
// Anlegen eines Objekts IImpot
IImpot impot = null;
try {
// Anlegen eines Objekts IImpot
impot = new FileImpot("DataImpot.txt");
} catch (FileImpotException e) {
// Fehleranzeige
string msg = e.InnerException == null ? null : String.Format(", Exception d'origine : {0}", e.InnerException.Message);
Console.WriteLine("L'erreur suivante s'est produite : [Code={0},Message={1}{2}]", e.Code, e.Message, msg == null ? "" : msg);
// Programmabbruch
Environment.Exit(1);
}
// Endlosschleife
while (true) {
...
}//while
}
}
}
- Zeile 8: Objekt impot vom Typ der Schnittstelle IImpot
- Zeile 11: Instanziierung des Objekts impot mit einem Objekt vom Typ FileImpot. Dies kann eine Ausnahme auslösen, die durch die try/catch-Anweisungen in den Zeilen 9, 12 und 18 abgefangen wird.
Hier sind einige Ausführungsbeispiele:
Mit der Datei [DataImpot.txt]
Mit einer nicht vorhandenen Datei [xx]
Mit der Datei [DataImpotInvalide.txt]






























