Skip to content

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:


public int Length { get; }
nombre de caractères de la chaîne

public bool EndsWith(string value)

rend vrai si la chaîne se termine par value

public bool StartsWith(string value)

rend vrai si la chaîne commence par value
public virtual bool Equals(object obj)

rend vrai si la chaînes est égale à obj - équivalent chaîne==obj

public int IndexOf(string value, int startIndex)

rend la première position dans la chaîne de la
chaîne value - la recherche commence à partir du
caractère n° startIndex

public int IndexOf(char value, int startIndex)

idem mais pour le caractère value

public string Insert(int startIndex, string value)

insère la chaîne value dans chaîne en position
startIndex
public static string Join(string separator, string[] value)

méthode de classe - rend une chaîne de caractères,
résultat de la concaténation des valeurs du tableau
value avec le séparateur separator

public int LastIndexOf(char value, int startIndex, int count)

public int LastIndexOf(string value, int startIndex, int count)

idem indexOf mais rend la dernière position au lieu
de la première

public string Replace(char oldChar, char newChar)

rend une chaîne copie de la chaîne courante où le
caractère oldChar a été remplacé par le caractère
newChar

public string[] Split(char[] separator)

la chaîne est vue comme une suite de champs séparés
par les caractères présents dans le tableau
separator. Le résultat est le tableau de ces champs

public string Substring(int startIndex, int length)

sous-chaîne de la chaîne courante commençant à la
position startIndex et ayant length caractères
public string ToLower()
rend la chaîne courante en minuscules
public string ToUpper()
rend la chaîne courante en majuscules
public string Trim()
rend la chaîne courante débarrassée de ses espaces
de début et fin

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:

uneChaine=l'oiseau vole au-dessus des nuages
uneChaine.Length=34
chaine[10]=o
uneChaine.IndexOf("vole")=9
uneChaine.IndexOf("x")=-1
uneChaine.LastIndexOf('a')=30
uneChaine.LastIndexOf('x')=-1
uneChaine.Substring(4,7)=seau vo
uneChaine.ToUpper()=L'OISEAU VOLE AU-DESSUS DES NUAGES
uneChaine.ToLower()=l'oiseau vole au-dessus des nuages
uneChaine.Replace('a','A')=l'oiseAu vole Au-dessus des nuAges
champs[0]=[l'oiseau]
champs[1]=[vole]
champs[2]=[au-dessus]
champs[3]=[des]
champs[4]=[nuages]
Join(":",champs)=l'oiseau:vole:au-dessus:des:nuages
("  abc  ").Trim()=[abc]

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:

1
2
3
4
5
6
Champs[0]=un
Champs[1]=deux
Champs[2]=
Champs[3]=trois
Champs[4]=
join=[un:deux::trois:]

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);
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.
résultat
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);
value
Zeichenfolgen-Array
separator
eine Zeichenkette, die als Feldtrennzeichen dient
résultat
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:

StringBuilder()
constructeur par défaut
StringBuilder(String value)

construction et initialisation avec value
StringBuilder(String value, int capacité)

construction et initialisation avec value avec une taille de
bloc de capacité caractères.

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:

taille=4, capacité=16
taille=8, capacité=16
taille=12, capacité=16
taille=16, capacité=16
taille=20, capacité=32
taille=24, capacité=32
taille=28, capacité=32
taille=32, capacité=32
taille=36, capacité=64
taille=40, capacité=64
taille=44, capacité=64
taille=4, capacité=10
taille=8, capacité=10
taille=12, capacité=20
taille=16, capacité=20
taille=20, capacité=20
taille=24, capacité=40
taille=28, capacité=40
taille=32, capacité=40
taille=36, capacité=40
taille=40, capacité=40
taille=44, capacité=80

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:


public StringBuilder Append(string value)

ajoute la chaîne value à l'objet StringBuilder. Rend
l'objet StringBuilder. Cette méthode est surchargée
 pour admettre différents types pour value : byte,
int, float, double, decimal, ... 

public StringBuilder Insert(int index,
string value)

insère value à la position index. Cette méthode est
surchargée comme la précédente pour accepter
différents types pour value.

public StringBuilder Remove(int index, int length)

supprime length caractères à partir de la position
index.

public StringBuilder Replace(string oldValue,
string newValue)

remplace dans StringBuilder, la chaîne oldValue par
la chaîne newValue. Il existe une version surchargée
(char oldChar, char newChar).
public String ToString()

convertit l'objet StringBuilder en un objet de type
String.

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:

XYZTstabCD

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

public int Length {get;}
nombre total d'éléments du tableau, quelque soit son nombre de dimensions
public int Rank {get;}
nombre total de dimensions du tableau

Methoden

public static int BinarySearch<T>(T[] tableau,
 value)
rend la position de [value] dans tableau.
public static int BinarySearch<T>(T[] tableau,
nt index, int length, T value)
idem mais cherche dans tableau à partir de la
position [index] et sur [length] éléments
public static void Clear(Array tableau, int index,
int length)
met les [length] éléments de tableau commençant au
 n° [index] à 0 si numériques, false si booléens, null si références
public static void Copy(Array source,
Array destination, int length)
copie [length] éléments de source dans destination
public int GetLength(int i)
nombre d'éléments de la dimension n° i du tableau
public int GetLowerBound(int i)
indice du 1er élément de la dimension n° i
public int GetUpperBound(int i)
indice du dernier élément de la dimension n° i
public static int IndexOf<T>(T[] tableau,
T valeur)
rend la position de valeur dans tableau ou -1 si
valeur n'est pas trouvée.
public static void Resize<T>(ref T[] tableau,
int n)
redimensionne tableau à n éléments. Les éléments
déjà présents sont conservés.
public static void Sort<T>(T[] tableau,
IComparer<T> comparateur)
trie tableau selon un ordre défini par comparateur.
Cette méthode a été présentée au paragraphe Erreur : source de la référence non trouvée.

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:

Elément (réel) 0 du tableau (rien pour terminer) : 3,6
Elément (réel) 1 du tableau (rien pour terminer) : 7,4
Elément (réel) 2 du tableau (rien pour terminer) : -1,5
Elément (réel) 3 du tableau (rien pour terminer) : -7
Elément (réel) 4 du tableau (rien pour terminer) :
Tableau non trié----------------------------------
3,6
7,4
-1,5
-7
Elément cherché (rien pour arrêter) :
7,4
Trouvé en position 1
Elément cherché (rien pour arrêter) :
0
Pas dans le tableau
Elément cherché (rien pour arrêter) :

Tableau trié--------------------------------------
-7
-1,5
3,6
7,4
Elément cherché (rien pour arrêter) :
7,4
Trouvé en position 3
Elément cherché (rien pour arrêter) :
0
Pas dans le tableau
Elément cherché (rien pour arrêter) :

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:

Image

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:

ArrayList liste=new ArrayList();
liste.Add(4);

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

public int Count {get;}
nombre d'éléments de la liste
public int Capacity {get;}
nombre d'éléments que la liste peut contenir avant d'être redimensionnée. Ce
redimensionnement se fait automatiquement. Cette notion de capacité de liste
est analogue à celle de capacité décrite pour la classe StringBuilder paragraphe Erreur : source de la référence non trouvée.

Methoden

public void Add(T item)
ajoute item à la liste
public int BinarySearch<T>(T item)
rend la position de item dans la liste s'il s'y
trouve sinon un nombre <0
public int BinarySearch<T>(T item,
IComparer<T> comparateur)
idem mais le 2ième paramètre permet de comparer deux
éléments de la liste. L'interface IComparer<T> a été
présentée au paragraphe Erreur : source de la référence non trouvée.
public void Clear()
supprime tous les éléments de la liste
public bool Contains(T item)
rend True si item est dans la liste, False sinon
public void CopyTo(T[] tableau)
copie les éléments de la liste dans tableau.
public int IndexOf(T item)
rend la position de item dans tableau ou -1 si
valeur n'est pas trouvée.
public void Insert(T item, int index)
insère item à la position index de la liste
public bool Remove(T item)
supprime item de la liste. Rend True si l'opération
réussit, False sinon.
public void RemoveAt(int index)
supprime l'élément n° index de la liste
public void Sort(IComparer<T> comparateur)
trie la liste selon un ordre défini par comparateur.
 Cette méthode a été présentée paragraphe Erreur : source de la référence non trouvée.
public void Sort()
trie la liste selon l'ordre défini par le type des
éléments de la liste
public T[] ToArray()
rend les éléments de la liste sous forme de tableau

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:
1
2
3
4
public interface IEnumerable<T>{
    IEnumerator GetEnumerator();
    IEnumerator<T> GetEnumerator();
}

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&lt;TKey,TValue&gt; 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:

1
2
3
4
5
TValue v=...;
TKey c=...;
Dictionary<TKey,TValue> D=new Dictionary<TKey,TValue>();
D[c]=v;
v=D[c];

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&lt;TKey,TValue&gt;*** sind folgende:

Konstruktoren

public Dictionary<TKey,TValue>()
constructeur sans paramètres - construit un dictionnaire vide.
Il existe plusieurs autres constructeurs.

Eigenschaften

public int Count {get;}
nombre d'entrées (clé, valeur) dans le dictionnaire
public Dictionary<TKey,TValue>.KeyCollection Keys {get;}
collection des clés du dictionnaire.
public Dictionary<TKey,TValue>.ValueCollection Values {get;}
collection des valeurs du dictionnaire.

Methoden

public void Add(TKey key, TValue value)
ajoute le couple (key, value) au dictionnaire
public void Clear()
supprime tous les couples du dictionnaire
public bool ContainsKey (TKey key)
rend True si key est une clé du dictionnaire,
False sinon
public bool ContainsValue (TValue value)
rend True si value est une valeur du dictionnaire,
False sinon
public void CopyTo(T[] tableau)
copie les éléments de la liste dans tableau.
public bool Remove(TKey key)
supprime du dictionnaire le couple de clé key.
Rend True si l'opération réussit, False sinon.
public bool TryGetValue(TKey key,
out TValue value)
rend dans value, la valeur associée à la clé key si
cette dernière existe, sinon rend la valeur par
défaut du type TValue (0 pour les nombres, false
pour les booléens, null pour les références d'objet)

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:

Le dictionnaire a 4 éléments
[Liste des clés]----------------------------------
jean
paul
mélanie
violette
[Liste des valeurs]-------------------------------
20
18
10
15
[Liste des clés & valeurs]
clé=jean valeur=20
clé=paul valeur=18
clé=mélanie valeur=10
clé=violette valeur=15
[Suppression d'une clé]
[Liste des clés & valeurs]
clé=jean valeur=20
clé=mélanie valeur=10
clé=violette valeur=15
Nom recherché (rien pour arrêter) : violette
violette,15
Nom recherché (rien pour arrêter) : x
Nom x inconnu

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

public StreamReader(string path)
construit un flux de lecture à partir du fichier de chemin path. Le
contenu du fichier peut être encodé de diverses façons. Il existe un
constructeur qui permet de préciser le codage utilisé. Par défaut,
c'est le codage UTF-8 qui est utilisé.

Eigenschaften

public bool EndOfStream {get;}
True si le flux a été lu entièrement

Methoden

public void Close()
ferme le flux et libère les ressources allouées pour
sa gestion. A faire obligatoirement après
exploitation du flux.
public override int Peek()
rend le caractère suivant du flux sans le consommer.
Un Peek supplémentaire rendrait donc le même
caractère.
public override int Read()
rend le caractère suivant du flux et avance d'un
caractère dans le flux.
public override int Read(char[] buffer,
int index, int count)
lit count caractères dans le flux et les met dans
buffer à partir de la position index. Rend le nombre
de caractères lus - peut être 0.
public override string ReadLine()
rend la ligne suivante du flux ou null si on était à
la fin du flux.
public override string ReadToEnd()
rend la fin du flux ou "" si on était à la fin du
flux.

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:

12620:0:0
13190:0,05:631
15640:0,1:1290,5

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:

1
2
3
4
5
6
7
8
9
Répertoire d'exécution : C:\data\2007-2008\c# 2008\poly\Chap3\07\bin\Release
Lecture 1----------------
12620:0:0
13190:0,05:631
15640:0,1:1290,5
Lecture 2----------------
12620:0:0
13190:0,05:631
15640:0,1:1290,5

Wenn man in Zeile 15 den Dateinamen xx.txt eingibt, erhält man folgende Ergebnisse:

1
2
3
Répertoire d'exécution : C:\data\2007-2008\c# 2008\poly\Chap3\07\bin\Release
Lecture 1----------------
L'erreur suivante s'est produite : Could not find file 'C:\...\Chap3\07\bin\Release\xx.txt'.

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

public StreamWriter(string path)
construit un flux d'écriture dans le fichier de chemin path. Le
contenu du fichier peut être encodé de diverses façons. Il existe un
constructeur qui permet de préciser le codage utilisé. Par défaut,
c'est le codage UTF-8 qui est utilisé.

Eigenschaften

public virtual bool AutoFlush
{get;set;}
fixe le mode d'écriture dans le fichier du buffer associé au flux. Si
égal à False, l'écriture dans le flux n'est pas immédiate : il y a
d'abord écriture dans une mémoire tampon puis dans le fichier lorsque la
mémoire tampon est pleine sinon l'écriture dans le fichier est immédiate
(pas de tampon intermédiaire). Par défaut c'est le mode tamponné qui est
utilisé. Le tampon n'est écrit dans le fichier que lorsqu'il est plein ou
bien lorsqu'on le vide explicitement par une opération Flush ou encore
lorsqu'on ferme le flux StreamWriter par une opération Close. Le mode
AutoFlush=False est le plus efficace lorsqu'on travaille avec des
fichiers parce qu'il limite les accès disque. C'est le mode par défaut
pour ce type de flux. Le mode AutoFlush=False ne convient pas à tous les
flux, notamment les flux réseau. Pour ceux-ci, qui souvent prennent place
dans un dialogue entre deux partenaires, ce qui est écrit par l'un des
partenaires doit être immédiatement lu par l'autre. Le flux d'écriture
doit alors être en mode AutoFlush=True.
public virtual string NewLine {get;set;}
les caractères de fin de ligne. Par défaut "\r\n". Pour un système Unix,
il faudrait utiliser "\n".

Methoden

public void Close()
ferme le flux et libère les ressources allouées pour sa
gestion. A faire obligatoirement après exploitation du flux.
public override void Flush()
écrit dans le fichier, le buffer du flux, sans attendre qu'il
soit plein.
public virtual void Write(T value)
écrit value dans le fichier associé au flux. Ici T n'est pas
un type générique mais symbolise le fait que la méthode
Write accepte différents types de paramètres (string, int,
object, ...). La méthode value.ToString est utilisée pour
produire la chaîne écrite dans le fichier.
public virtual void WriteLine(T value)
même chose que Write mais avec la marque de fin de ligne
(NewLine) en plus.

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:

1
2
3
4
5
Répertoire d'exécution : C:\data\2007-2008\c# 2008\poly\Chap3\07\bin\Release
Mode AutoFlush : False
ligne (rien pour arrêter) : 1ère ligne
ligne (rien pour arrêter) : 2ième ligne
ligne (rien pour arrêter) :

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:

1
2
3
4
5
6
7
8
9
paul : 10
helene : 15

jacques : 11
sylvain : 12
xx : -1

xx: yy : zz
xx : yy

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 Daten werden in die Binärdatei geschrieben
            output.Write(nom);
            output.Write(age);

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
        output.Close();

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]:

1
2
3
4
5
6
7
8
9
paul : 10
helene : 15

jacques : 11
sylvain : 12
xx : -1

xx: yy : zz
xx : yy

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:
1
2
3
La ligne n° [6] du fichier [personnes1.txt] a un âge [-1] incorrect
La ligne n° [8] du fichier [personnes1.txt] a un nombre de champs incorrect
La ligne n° [9] du fichier [personnes1.txt] a un âge [yy] incorrect

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
nom=input.ReadString().Trim();
age=input.ReadInt32();

Die Klasse BinaryReader verfügt über verschiedene Methoden ReadXX zum Lesen der verschiedenen einfachen Datentypen

  • Zeile 60: Der Vorgang zum Schließen des Datenstroms
        input.Close();

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:

1
2
3
4
paul:10
helene:15
jacques:11
sylvain:12

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:

Caractère
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.
(modèle) 
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 „\)“.
x|y
Entspricht entweder x oder y. Beispielsweise entspricht „z|foot“ entweder „z“ oder „foot“. „(z|f)oo“ entspricht entweder „zoo“ oder „foo“.
{n}
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,} 
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*“.
{n,m} 
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?“.
[xyz] 
Zeichensatz. Entspricht einem der angegebenen Zeichen. Beispielsweise entspricht „[abc]“ dem Zeichen „a“ in „plat“.
[^xyz] 
Negativer Zeichensatz. Entspricht jedem nicht angegebenen Zeichen. Beispielsweise entspricht „[^abc]“ dem Zeichen „p“ in „plat“.
[a-z] 
Zeichenbereich. Entspricht jedem Zeichen in der angegebenen Zeichenfolge. Beispielsweise entspricht „[a-z]“ jedem Kleinbuchstaben zwischen „a“ und „z“.
[^m-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.
\b 
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“.
\B 
Entspricht einer Grenze, die kein Wort darstellt. „en*t\B“ entspricht „ent“ in „bien entendu“.
\d 
Entspricht einem Zeichen, das eine Ziffer darstellt. Entspricht [0-9].
\D 
Entspricht einem Zeichen, das keine Ziffer darstellt. Entspricht [^0-9].
\f 
Entspricht einem Seitenumbruchzeichen.
\n 
Entspricht einem Zeilenumbruchzeichen.
\r 
Entspricht einem Wagenrücklaufzeichen.
\s 
Entspricht allen Leerzeichen, einschließlich Leerzeichen, Tabulatoren, Seitenumbrüchen usw. Entspricht „[ \f\n\r\t\v]“.
\S 
Entspricht jedem nicht-leeren Leerzeichen. Entspricht „[^ \f\n\r\t\v]“.
\t 
Entspricht einem Tabulatorzeichen.
\v 
Entspricht einem vertikalen Tabulatorzeichen.
\w 
Entspricht jedem Zeichen, das ein Wort darstellt und einen Unterstrich enthält. Entspricht „[A-Za-z0-9_]“.
\W 
Entspricht jedem Zeichen, das kein Wort darstellt. Entspricht „[^A-Za-z0-9_]“.
\num 
Entspricht num, wobei num eine positive ganze Zahl ist. Bezieht sich auf gespeicherte Zuordnungen. Beispielsweise entspricht „(.)\1“ zwei aufeinanderfolgenden identischen Zeichen.
\n
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.
\xn
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:

public Regex(string pattern)
construit un objet "expression régulière" à partir d'un modèle passé
en paramètre (pattern)

Sobald der reguläre Ausdruck als Vorlage erstellt ist, kann er mit der Methode IsMatch mit Zeichenfolgen verglichen werden:

public bool IsMatch(string input)
vrai si la chaîne input correspond au modèle de l'expression
régulière

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:

[  123  ] correspond au modèle [^\s*\d+\s*$]
[  123a  ] ne correspond pas au modèle [^\s*\d+\s*$]

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:

public MatchCollection Matches(string input)
rend la collection des éléments de la chaîne input
correspondant au modèle

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:

1
2
3
4
5
Modèle=[\d+],exemplaire=[  123  456  789 ]
Il y a 3 occurrences du modèle dans l'exemplaire
[123] trouvé en position 2
[456] trouvé en position 7
[789] trouvé en position 12

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:

1
2
3
4
5
L'exemplaire [Il est 18:05:49] correspond au modèle [(\d\d):(\d\d):(\d\d)]
groupes[0]=[18:05:49] trouvé en position 7
groupes[1]=[18] trouvé en position 7
groupes[2]=[05] trouvé en position 10
groupes[3]=[49] trouvé en position 13

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:

Tapez le modèle à tester ou rien pour arrêter :\d+
Tapez la chaîne à comparer au modèle [\d+] ou rien pour arrêter :123 456 789
J'ai trouvé la correspondance [123] en position [0]
J'ai trouvé la correspondance [456] en position [4]
J'ai trouvé la correspondance [789] en position [8]
Tapez la chaîne à comparer au modèle [\d+] ou rien pour arrêter :
Tapez le modèle à tester ou rien pour arrêter :(\d{2}):(\d\d)
Tapez la chaîne à comparer au modèle [(\d{2}):(\d\d)] ou rien pour arrêter :14:15 abcd 17:18 xyzt
J'ai trouvé la correspondance [14:15] en position [0]
        sous-élément [14] en position [0]
        sous-élément [15] en position [3]
J'ai trouvé la correspondance [17:18] en position [11]
        sous-élément [17] en position [11]
        sous-élément [18] en position [14]
Tapez la chaîne à comparer au modèle [(\d{2}):(\d\d)] ou rien pour arrêter :
Tapez le modèle à tester ou rien pour arrêter :^\s*\d+\s*$
Tapez la chaîne à comparer au modèle [^\s*\d+\s*$] ou rien pour arrêter :   1456
J'ai trouvé la correspondance [   1456] en position [0]
Tapez la chaîne à comparer au modèle [^\s*\d+\s*$] ou rien pour arrêter :
Tapez le modèle à tester ou rien pour arrêter :^\s*(\d+)\s*$
Tapez la chaîne à comparer au modèle [^\s*(\d+)\s*$] ou rien pour arrêter :1456
J'ai trouvé la correspondance [1456] en position [0]
        sous-élément [1456] en position [0]
Tapez la chaîne à comparer au modèle [^\s*(\d+)\s*$] ou rien pour arrêter :abcd 1456
Je n'ai pas trouvé de correspondances
Tapez la chaîne à comparer au modèle [^\s*(\d+)\s*$] ou rien pour arrêter :
Tapez le modèle à tester ou rien pour arrêter :

5.7.5. Die Methode „Split“

Diese Methode haben wir bereits in der Klasse String kennengelernt:


public string[] Split(char[] separator)
la chaîne est vue comme une suite de champs séparés par les
caractères présents dans le tableau separator. Le résultat est
le tableau de ces champs

Die Methode Split der Klasse Regex ermöglicht es uns, das Trennzeichen anhand einer Vorlage festzulegen:


public string[] Split(string input)
La chaîne input est décomposée en champs, ceux-ci étant séparés
par un séparateur correspondant au modèle de l'objet Regex
courant.

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:

string[] champs=new Regex(@"s*,\s*").Split(ligne);

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:

1
2
3
champs[0]=[abc]
champs[1]=[def]
champs[2]=[ghi]

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:

a:b:c

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]

1
2
3
Paramètres du calcul de l'Impot au format : Marié (o/n) NbEnfants Salaire ou rien pour arrêter :o 2 60000
Impot=4282 euros
Paramètres du calcul de l'Impot au format : Marié (o/n) NbEnfants Salaire ou rien pour arrêter :

Mit einer nicht vorhandenen Datei [xx]

L'erreur suivante s'est produite : [Code=Acces,Message=Erreur lors de la lecture du fichier xx, Exception d'origine : Could not find file 'C:\data\2007-2008\c#2008\poly\Chap3\10\bin\Release\xx'.]

Mit der Datei [DataImpotInvalide.txt]

L'erreur suivante s'est produite : [Code=Champ1, Champ2, Champ3,Message=Ligne n° 1 incorrecte]