Skip to content

5. Veelgebruikte klassen van .NET

Hier presenteren we enkele veelgebruikte klassen van het .NET-platform. Eerst laten we zien hoe u informatie kunt verkrijgen over de enkele honderden beschikbare klassen. Deze hulp is onmisbaar voor zelfs de meest ervaren C#-ontwikkelaar. De kwaliteit van de documentatie (gemakkelijke toegang, overzichtelijke indeling, relevantie van de informatie, ...) kan het succes of de mislukking van een ontwikkelomgeving bepalen.

5.1. Hulp zoeken over de .NET-klassen

Hier geven we enkele tips om hulp te vinden met Visual Studio.NET

5.1.1. Help/Inhoud

  • in [1], kies de optie Help/Contents uit het menu.
  • in [2], kies de optie Visual C# Express Edition
  • in [3], de helpboom voor C#
  • in [4] is een andere handige optie .NET Framework, die toegang geeft tot alle klassen van het .NET-framework.

Laten we eens kijken naar de hoofdstukkopjes in de C#-help:

  • [1]: een overzicht van C#
  • [2]: een reeks voorbeelden over bepaalde aspecten van C#
  • [3]: een cursus C# – zou dit document met voordeel kunnen vervangen…
  • [4]: om dieper in te gaan op de details van C#
  • [5]: nuttig voor C++- of Java-ontwikkelaars. Helpt enkele valkuilen te vermijden.
  • [6]: als je op zoek bent naar voorbeelden, kun je hier beginnen.
  • [7]: wat je moet weten om grafische gebruikersinterfaces te maken
  • [8]: om IDE Visual Studio Express beter te gebruiken
  • [9]: SQL Server Express 2005 is een gratis aangeboden, hoogwaardige SGBD. We zullen deze in deze cursus gebruiken.

De C#-help is slechts een deel van wat de ontwikkelaar nodig heeft. Het andere deel is de help bij de honderden klassen van het .NET-framework die zijn werk zullen vergemakkelijken.

  • [1]: selecteer de help over het .NET-framework
  • [2]: de helpfunctie bevindt zich in de tak .NET Framework SDK
  • [3]: de tak .NET Framework Class Library bevat alle klassen .NET op basis van de naamruimte waartoe ze behoren
  • [4]: de naamruimte System die het vaakst werd gebruikt in de voorbeelden uit de voorgaande hoofdstukken
  • [5]: in de naamruimte System, een voorbeeld, in dit geval de structuur DateTime
  • [6]: de helptekst over de structuur DateTime

5.1.2. Help/Index/Zoeken

De helpfunctie van MSDN is enorm uitgebreid en het kan lastig zijn om te weten waar je moet zoeken. Je kunt dan de index van de helpfunctie gebruiken:

  • in [1], gebruik de optie [Help/Index] als het helpvenster nog niet openstaat, anders gebruik je [2] in een bestaand helpvenster.
  • naar [3], geef aan in welk domein er gezocht moet worden
  • in [4], geef aan wat u zoekt, in dit geval een klas
  • in [5]: het antwoord

Een andere manier om hulp te zoeken, is door gebruik te maken van de zoekfunctie in de help:

  • in [1], gebruik de optie [Help/Search] als het helpvenster nog niet geopend is, anders gebruik [2] in een bestaand helpvenster.
  • naar [3], geef aan wat er gezocht wordt
  • in [4], filter de zoekgebieden
  • in [5]: het resultaat weergeven in de vorm van verschillende thema’s waarin de gezochte tekst is gevonden.

5.2. De tekenreeksen

5.2.1. De klasse System.String

De klasse System.String is identiek aan het eenvoudige type string. Deze beschikt over talrijke eigenschappen en methoden. Hier volgen er enkele:


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

Er is één belangrijk punt om op te merken: wanneer een methode een tekenreeks retourneert, is deze een andere tekenreeks dan de tekenreeks waarop de methode is toegepast. Zo retourneert S1.Trim() de tekenreeks S2, en zijn S1 en S2 twee verschillende tekenreeksen.

Een tekenreeks C kan worden beschouwd als een array van tekens. Zo

  • is C[i] het i-de teken van C
  • is C.Length het aantal tekens van C

Laten we het volgende voorbeeld eens bekijken:


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() + "]");
        }//Hoofd

        public static void affiche(string msg) {
            // geeft bericht weer
            Console.WriteLine(msg);
        }//toont
    }//klasse
}//naamruimte

De uitvoering levert de volgende resultaten op:

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]

Laten we een nieuw voorbeeld bekijken:


using System;

namespace Chap3 {
    class Program {
        static void Main(string[] args) {
            // de te analyseren regel
            string ligne = "un:deux::trois:";
            // de veldscheidingstekens
            char[] séparateurs = new char[] { ':' };
            // split
            string[] champs = ligne.Split(séparateurs);
            for (int i = 0; i < champs.Length; i++) {
                Console.WriteLine("Champs[" + i + "]=" + champs[i]);
            }
            // samenvoegen
            Console.WriteLine("join=[" + System.String.Join(":", champs) + "]");
        }
    }
}

en de uitvoerresultaten:

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

Met de methode Split van de klasse String kunnen elementen van een tekenreeks in een array worden geplaatst. De definitie van de hier gebruikte methode Split is als volgt:


    public string[] Split(char[] separator);
separator
tekenreeks. Deze tekens geven de tekens weer die worden gebruikt om de velden in de tekenreeks van elkaar te scheiden. Als de tekenreeks bijvoorbeeld "champ1, champ2, champ3"" is, kunnen we separator=new char[] {','} gebruiken. Als het scheidingsteken een reeks spaties is, gebruiken we separator=null.
résultat
array van tekenreeksen waarbij elk element van de array een veld van de tekenreeks is.

De methode Join is een statische methode van de klasse String:


    public static string Join(string separator, string[] value);
value
array van tekenreeksen
separator
een tekenreeks die als veldscheidingsteken zal dienen
résultat
een tekenreeks die is gevormd door de elementen van de tabel value aan elkaar te plakken, gescheiden door de tekenreeks separator.

5.2.2. De klasse System.Text.StringBuilder

Eerder hebben we gezegd dat de methoden van de klasse String die werden toegepast op een tekenreeks S1 een andere tekenreeks S2 opleverden. Met de klasse System.Text.StringBuilder kun je S1 bewerken zonder dat je een tekenreeks S2 hoeft aan te maken. Dit verbetert de prestaties doordat het vermijdt dat er veel tekenreeksen met een zeer beperkte levensduur worden aangemaakt.

De klasse ondersteunt verschillende constructors:

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.

Een StringBuilder-object werkt met blokken van capacité tekens om de onderliggende tekenreeks op te slaan. Standaard is capacité gelijk aan 16. Met de derde constructor hierboven kan de capaciteit van de blokken worden gespecificeerd. Het aantal blokken van capacité tekens dat nodig is om een tekenreeks S op te slaan, wordt automatisch aangepast door de klasse StringBuilder. Er zijn constructors om het maximale aantal tekens in een StringBuilder-object vast te leggen. Standaard is deze maximale capaciteit 2 147 483 647.

Hier volgt een voorbeeld dat dit concept van capacité illustreert:


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);
            }
        }
    }
}
  • regel 7: aanmaken van een object StringBuilder met een blokgrootte van 16 tekens
  • regel 8: str.Length is het huidige aantal tekens van de tekenreeks str. str.Capacity is het aantal tekens dat de huidige tekenreeks str kan bevatten voordat er een nieuw blok wordt toegewezen.
  • regel 10: Met str.Append(String S) kan de tekenreeks S van het type String worden samengevoegd met de tekenreeks str van het type StringBuilder.
  • regel 14: aanmaken van een object StringBuilder met een blokcapaciteit van 10 tekens

Het resultaat van de uitvoering:

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

Deze resultaten tonen aan dat de klasse een eigen algoritme volgt om nieuwe blokken toe te wijzen wanneer de capaciteit onvoldoende is:

  • regels 4-5: uitbreiding van de capaciteit met 16 tekens
  • regels 8-9: uitbreiding van de capaciteit met 32 tekens, terwijl 16 voldoende zouden zijn geweest.

Hieronder volgen enkele methoden van de 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 is een voorbeeld:


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

en de resultaten daarvan:

XYZTstabCD

5.3. De arrays

De tabellen zijn afgeleid van de klasse Array:

De klasse Array beschikt over diverse methoden om een array te sorteren, een element in een array te zoeken, de grootte van een array aan te passen, ... We stellen enkele eigenschappen en methoden van deze klasse voor. Ze zijn vrijwel allemaal overladen, c.a.d, wat betekent dat ze in verschillende varianten bestaan. Elke array erft deze.

Eigenschappen

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.

Het volgende programma illustreert het gebruik van bepaalde methoden van de klasse Array:


using System;

namespace Chap3 {
    class Program {
        // zoektype
        enum TypeRecherche { linéaire, dichotomique };

        // hoofdmethode
        static void Main(string[] args) {
            // elementen van een array inlezen die via het toetsenbord zijn ingevoerd
            double[] éléments;
            Saisie(out éléments);
            // weergave van een ongesorteerde tabel
            Affiche("Tableau non trié", éléments);
            // Lineair zoeken in de ongesorteerde tabel
            Recherche(éléments, TypeRecherche.linéaire);
            // sorteren van de tabel
            Array.Sort(éléments);
            // weergave van de gesorteerde tabel
            Affiche("Tableau trié", éléments);
            // Dichotoom zoeken in de gesorteerde tabel
            Recherche(éléments, TypeRecherche.dichotomique);
        }

        // Waarden invoeren in de tabel 'elementen'
        // elementen: verwijzing naar de tabel die door de methode is aangemaakt
        static void Saisie(out double[] éléments) {
            bool terminé = false;
            string réponse;
            bool erreur;
            double élément = 0;
            int i = 0;
            // in het begin bestaat de tabel niet
            éléments = null;
            // lus voor het invoeren van de elementen van de tabel
            while (!terminé) {
                // vraag
                Console.Write("Elément (réel) " + i + " du tableau (rien pour terminer) : ");
                // het antwoord uitlezen
                réponse = Console.ReadLine().Trim();
                // einde van de invoer als de tekenreeks leeg is
                if (réponse.Equals(""))
                    break;
                // controle van de invoer
                try {
                    élément = Double.Parse(réponse);
                    erreur = false;
                } catch {
                    Console.Error.WriteLine("Saisie incorrecte, recommencez");
                    erreur = true;
                }//try-catch
                // als er geen fout is
                if (!erreur) {
                    // nog een element aan de array toevoegen
                    i += 1;
                    // de array aanpassen om het nieuwe element op te nemen
                    Array.Resize(ref éléments, i);
                    // nieuw element invoegen
                    éléments[i - 1] = élément;
                }
            }//while
        }

        // generieke methode om de elementen van een array weer te geven
        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);
            }
        }

        // zoeken naar een element in de array
        // elementen: array van reële getallen
        // TypeRecherche: dichotoom of lineair
        static void Recherche(double[] éléments, TypeRecherche type) {
            // Zoeken
            bool terminé = false;
            string réponse = null;
            double élément = 0;
            bool erreur = false;
            int i = 0;
            while (!terminé) {
                // vraag
                Console.WriteLine("Elément cherché (rien pour arrêter) : ");
                // antwoord lezen en controleren
                réponse = Console.ReadLine().Trim();
                // klaar?
                if (réponse.Equals(""))
                    break;
                // controle
                try {
                    élément = Double.Parse(réponse);
                    erreur = false;
                } catch {
                    Console.WriteLine("Erreur, recommencez...");
                    erreur = true;
                }//try-catch
                // als er geen fout is
                if (!erreur) {
                    // we zoeken het element in de array
                    if (type == TypeRecherche.dichotomique)
                        // dichotomisch zoeken
                        i = Array.BinarySearch(éléments, élément);
                    else
                        // lineair zoeken
                        i = Array.IndexOf(éléments, élément);
                    // Weergave van het antwoord
                    if (i >= 0)
                        Console.WriteLine("Trouvé en position " + i);
                    else
                        Console.WriteLine("Pas dans le tableau");
                }//if
            }//while
        }
    }
}
  • regels 27-62: de methode Saisie voert de elementen van een array éléments in die via het toetsenbord zijn ingevoerd. Aangezien de array niet vooraf kan worden gedimensioneerd (de uiteindelijke grootte is niet bekend), moet deze bij elk nieuw element opnieuw worden aangepast (regel 57). Een efficiënter algoritme zou zijn geweest om ruimte aan de array toe te wijzen per groep van N elementen. Een array is echter niet bedoeld om van grootte te worden aangepast. Dit geval kan beter worden afgehandeld met een lijst (ArrayList, List<T>).
  • regels 75-113: met de methode Recherche kan in de array éléments worden gezocht naar een element dat via het toetsenbord is ingevoerd. De zoekmethode verschilt naargelang de array al dan niet gesorteerd is. Voor een ongesorteerde array wordt een lineaire zoekopdracht uitgevoerd met de methode IndexOf uit regel 106. Voor een gesorteerde array wordt een dichotomische zoekopdracht uitgevoerd met de methode BinarySearch uit regel 103.
  • regel 18: de tabel wordt gesorteerd met éléments. Hier wordt een variant van Sort gebruikt die slechts één parameter heeft: de te sorteren tabel. De volgorde die wordt gebruikt om de elementen van de array te vergelijken, is dan de impliciete volgorde van deze elementen. In dit geval zijn de elementen getallen. Er wordt gebruikgemaakt van de natuurlijke volgorde van getallen.

De schermresultaten zijn als volgt:

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. Generieke collecties

Naast de tabel zijn er verschillende klassen om verzamelingen van elementen op te slaan. Er zijn generieke versies in de naamruimte System.Collections.Generic en niet-generieke versies in System.Collections. We zullen twee veelgebruikte generieke verzamelingen bespreken: de lijst en het woordenboek.

De lijst met generieke verzamelingen is als volgt:

Image

5.4.1. De generieke klasse List<T>

Met de klasse System.Collections.Generic.List<T> kunnen verzamelingen van objecten van het type T worden geïmplementeerd waarvan de grootte tijdens de uitvoering van het programma varieert. Een object van het type List<T> kan bijna net als een array worden gebruikt. Zo wordt het element i van een lijst l aangeduid als l[i].

Er bestaat ook een niet-generiek lijsttype: ArrayList, dat verwijzingen naar willekeurige objecten kan opslaan. ArrayList is functioneel gelijk aan List<Object>.. Een object van het type ArrayList ziet er als volgt uit:

Hierboven verwijzen de elementen 0, 1 en i in de lijst naar objecten van verschillende typen. Een object moet eerst worden aangemaakt voordat de verwijzing ernaar aan de lijst ArrayList kan worden toegevoegd. Hoewel een ArrayList objectverwijzingen opslaat, is het mogelijk om er getallen in op te slaan. Dit gebeurt via een mechanisme dat Boxing heet: het getal wordt ingekapseld in een object O van het type Object en het is de verwijzing naar O die in de lijst wordt opgeslagen. Dit mechanisme is transparant voor de ontwikkelaar. Men kan dus schrijven:

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

Dit levert het volgende resultaat op:

Hierboven is het getal 4 ingekapseld in een O-object en wordt de O-referentie in de lijst opgeslagen. Om deze op te halen, kunnen we het volgende schrijven:


            int i = (int)liste[0];

De bewerking Object -> int wordt aangeduid als Unboxing. Als een lijst volledig bestaat uit typen int, verbetert het de prestaties om deze te declareren als List<int>. Getallen van het type int worden dan namelijk in de lijst zelf opgeslagen en niet in Object-typen buiten de lijst. Er vinden dan geen boxing- en unboxing-bewerkingen meer plaats.

Als een object van het type List<T> of T een klasse is, slaat de lijst ook hier de verwijzingen naar objecten van het type T op:

Hieronder volgen enkele eigenschappen en methoden van generieke lijsten:

Eigenschappen

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

Laten we het eerder behandelde voorbeeld met een object van het type Array nog eens bekijken en dit nu verwerken met een object van het type List<T>.. Omdat de lijst een object is dat veel weg heeft van een array, verandert de code nauwelijks. We geven alleen de opvallende wijzigingen weer:


using System;
using System.Collections.Generic;

namespace Chap3 {
    class Program {
        // zoektype
        enum TypeRecherche { linéaire, dichotomique };

        // hoofdmethode
        static void Main(string[] args) {
            // elementen uit een lijst lezen die via het toetsenbord zijn ingevoerd
            List<double> éléments;
            Saisie(out éléments);
            // aantal elementen
            Console.WriteLine("La liste a {0} éléments et une capacité de {1} éléments", éléments.Count, éléments.Capacity);
            // weergave van de ongesorteerde lijst
            Affiche("Liste non triée", éléments);
            // Lineair zoeken in de ongesorteerde lijst
            Recherche(éléments, TypeRecherche.linéaire);
            // sorteren van de lijst
            éléments.Sort();
            // weergave van de gesorteerde lijst
            Affiche("Liste triée", éléments);
            // Dichotoom zoeken in de gesorteerde lijst
            Recherche(éléments, TypeRecherche.dichotomique);
        }

        // Waarden invoeren in de lijst met elementen
        // elementen: verwijzing naar de lijst die door de methode is aangemaakt
        static void Saisie(out List<double> éléments) {
...
            // in het begin is de lijst leeg
            éléments = new List<double>();
            // lus voor het invoeren van de elementen van de lijst
            while (!terminé) {
...
                // als er geen fout is
                if (!erreur) {
                    // nog een element aan de lijst toevoegen
                    éléments.Add(élément);
                }
            }//while
        }

        // generieke methode om de elementen van een opsombaar object weer te geven
        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);
            }
        }

        // zoeken naar een element in de lijst
        // elementen: lijst met reële getallen
        // TypeRecherche: dichotoom of lineair
        static void Recherche(List<double> éléments, TypeRecherche type) {
...
            while (!terminé) {
...
                // als er geen fout is
                if (!erreur) {
                    // wordt het element in de lijst gezocht
                    if (type == TypeRecherche.dichotomique)
                        // dichotomisch zoeken
                        i = éléments.BinarySearch(élément);
                    else
                        // lineair zoeken
                        i = éléments.IndexOf(élément);
                    // Weergave van het antwoord
...
                }//if
            }//while
        }
    }
}
  • regels 46-51: de generieke methode Affiche<T> heeft twee parameters:
  • de eerste parameter is een tekst die moet worden geschreven
  • de tweede parameter is een object dat de generieke interface IEnumerable<T> implementeert:
1
2
3
4
public interface IEnumerable<T>{
    IEnumerator GetEnumerator();
    IEnumerator<T> GetEnumerator();
}

De structuur foreach( T element in elements) in regel 48 is geldig voor elk object van het type *éléments dat de interface *IEnumerable implementeert. Arrays (Array) en lijsten (List<T>) implementeren de interface IEnumerable<T>. Daarom is de methode Affiche zowel geschikt voor het weergeven van tabellen als van lijsten.

De uitvoerresultaten van het programma zijn dezelfde als in het voorbeeld met de klasse Array.

5.4.2. De klasse Dictionary<TKey,TValue>

Met de klasse System.Collections.Generic.Dictionary<TKey,TValue> kan een woordenboek worden geïmplementeerd. Een woordenboek kan worden gezien als een tabel met twee kolommen:

sleutel
waarde
sleutel1
waarde1
sleutel2
waarde2
..
...

In de klasse Dictionary<TKey,TValue> zijn de sleutels van het type Tkey en de waarden van het type TValue. De sleutels zijn uniek, c.a.d, wat betekent dat er geen twee identieke sleutels kunnen zijn. Zo’n woordenboek zou er als volgt uit kunnen zien als de typen TKey en TValue naar klassen zouden verwijzen:

De waarde die is gekoppeld aan sleutel C van een woordenboek D wordt weergegeven met de notatie D[C]. Deze waarde is zowel leesbaar als schrijfbaar. We kunnen dus schrijven:

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

Als de sleutel c niet bestaat in het woordenboek D, genereert de notatie D[c] een uitzondering.

De belangrijkste methoden en eigenschappen van de klasse ***Dictionary&lt;TKey,TValue&gt;*** zijn de volgende:

Constructors

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

Eigenschappen

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)

Laten we eens kijken naar het volgende voorbeeldprogramma:


using System;
using System.Collections.Generic;

namespace Chap3 {
    class Program {
        static void Main(string[] args) {
            // een woordenboek <string,int> aanmaken
            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
            // aantal elementen in het woordenboek
            Console.WriteLine("Le dictionnaire a " + dico.Count + " éléments");
            // lijst met sleutels
            Affiche("[Liste des clés]",dico.Keys);
            // lijst met waarden
            Affiche("[Liste des valeurs]", dico.Values);
            // lijst met sleutels en waarden
            Console.WriteLine("[Liste des clés & valeurs]");
            foreach (string clé in dico.Keys) {
                Console.WriteLine("clé=" + clé + " valeur=" + dico[clé]);
            }
            // de sleutel "paul" wordt verwijderd
            Console.WriteLine("[Suppression d'une clé]");
            dico.Remove("paul");
            // lijst met sleutels en waarden
            Console.WriteLine("[Liste des clés & valeurs]");
            foreach (string clé in dico.Keys) {
                Console.WriteLine("clé=" + clé + " valeur=" + dico[clé]);
            }
            // zoeken in het woordenboek
            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");
                }
                // volgende zoekopdracht
                Console.Out.Write("Nom recherché (rien pour arrêter) : ");
                nomCherché = Console.ReadLine().Trim();
            }//while
        }

        // generieke methode om de elementen van een opsombaar type weer te geven
        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);
            }
        }

    }
}
  • regel 8: een array van string die zal worden gebruikt om het woordenboek <string,int> te initialiseren
  • regel 11: het woordenboek <string,int>
  • regels 12-15: de initialisatie ervan op basis van de array van string uit regel 8
  • regel 17: aantal vermeldingen in het woordenboek
  • regel 19: de sleutels van het woordenboek
  • regel 21: de waarden van het woordenboek
  • regel 29: verwijdering van een item uit het woordenboek
  • regel 41: zoeken naar een sleutel in het woordenboek. Als deze niet bestaat, zal de methode TryGetValue de waarde 0 in value plaatsen, omdat value van het numerieke type is. Deze techniek is hier alleen bruikbaar omdat we weten dat de waarde 0 niet in het woordenboek voorkomt.

De uitvoerresultaten zijn als volgt:

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. De tekstbestanden

5.5.1. De klasse StreamReader

Met de klasse System.IO.StreamReader kan de inhoud van een tekstbestand worden gelezen. Deze klasse kan in feite ook gegevensstromen verwerken die geen bestanden zijn. Hieronder volgen enkele van de eigenschappen en methoden:

Constructors

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é.

Eigenschappen

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 is een voorbeeld:


using System;
using System.IO;

namespace Chap3 {
    class Program {
        static void Main(string[] args) {
            // uitvoeringsmap
            Console.WriteLine("Répertoire d'exécution : "+Environment.CurrentDirectory);
            string ligne = null;
            StreamReader fluxInfos = null;
            // inhoud van het bestand lezen infos.txt
            try {
                // lezen 1
                Console.WriteLine("Lecture 1----------------");
                using (fluxInfos = new StreamReader("infos.txt")) {
                    ligne = fluxInfos.ReadLine();
                    while (ligne != null) {
                        Console.WriteLine(ligne);
                        ligne = fluxInfos.ReadLine();
                    }
                }
                // lezen 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);
            }
        }
    }
}
  • regel 8: geeft de naam van de uitvoermap weer
  • regels 12, 27: een try/catch-blok om een eventuele uitzondering af te vangen.
  • regel 15: de structuur using flux=new StreamReader(...) is een hulpmiddel om de stream niet expliciet te hoeven sluiten na gebruik. Deze sluiting gebeurt automatisch zodra men het bereik van using verlaat.
  • regel 15: het gelezen bestand heet infos.txt. Aangezien dit een relatieve naam is, wordt het gezocht in de uitvoermap die in regel 8 wordt weergegeven. Als het daar niet staat, wordt er een uitzondering gegenereerd die door de try/catch wordt afgehandeld.
  • regels 16-20: het bestand wordt regel voor regel ingelezen
  • regel 25: het bestand wordt in één keer gelezen

Het bestand infos.txt ziet er als volgt uit:

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

en is geplaatst in de volgende map van het C#-project:

We zullen zien dat bin/Release de uitvoermap is wanneer het project wordt uitgevoerd met Ctrl-F5.

De uitvoering levert de volgende resultaten op:

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

Als we op regel 15 de bestandsnaam xx.txt invoeren, krijgen we de volgende resultaten:

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. De klasse StreamWriter

Met de klasse System.IO.StreamReader kun je naar een tekstbestand schrijven. Net als de klasse StreamReader kan deze klasse ook stromen verwerken die geen bestanden zijn. Hieronder volgen enkele van de eigenschappen en methoden:

Constructors

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é.

Eigenschappen

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.

Laten we het volgende voorbeeld eens bekijken:


using System;
using System.IO;

namespace Chap3 {
    class Program2 {
        static void Main(string[] args) {
            // uitvoermap
            Console.WriteLine("Répertoire d'exécution : " + Environment.CurrentDirectory);
            string ligne = null;                        // een tekstregel
            StreamWriter fluxInfos = null;    // het tekstbestand
            try {
                // aanmaken van het tekstbestand
                using (fluxInfos = new StreamWriter("infos2.txt")) {
                    Console.WriteLine("Mode AutoFlush : {0}", fluxInfos.AutoFlush);
                    // lezen van de via het toetsenbord ingevoerde regel
                    Console.Write("ligne (rien pour arrêter) : ");
                    ligne = Console.ReadLine().Trim();
                    // lus zolang de ingevoerde regel niet leeg is
                    while (ligne != "") {
                        // regel naar tekstbestand schrijven
                        fluxInfos.WriteLine(ligne);
                        // nieuwe regel van het toetsenbord lezen
                        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);
            }
        }
    }
}
  • regel 13: ook hier gebruiken we de syntaxis using(flux), zodat we de stream niet expliciet hoeven te sluiten met een Close-bewerking. Deze sluiting gebeurt automatisch bij het verlaten van de using.
  • Waarom een try/catch, regels 11 en 27? Op regel 13 zouden we een bestandsnaam kunnen opgeven in de vorm /rep1/rep2/ .../fichier met een pad /rep1/rep2/... dat niet bestaat, waardoor het onmogelijk wordt om fichier aan te maken. Er zou dan een uitzondering worden gegenereerd. Er zijn nog andere mogelijke uitzonderingsgevallen (schijf vol, onvoldoende rechten, ...)

De uitvoerresultaten zijn als volgt:

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

Het bestand infos2.txt is aangemaakt in de map bin/Release van het project:

 

5.6. De binaire bestanden

De klassen System.IO.BinaryReader en System.IO.BinaryWriter worden gebruikt voor het lezen en schrijven van binaire bestanden.

Laten we eens kijken naar de volgende toepassing:

// syntaxis pg tekst bin logs
// een tekstbestand (text) wordt ingelezen en de inhoud ervan wordt opgeslagen in een binair bestand (bin
// het tekstbestand bevat regels in de vorm naam : leeftijd die we zullen opslaan in een structuur met string en int
// (logs) is een tekstbestand met logbestanden

Het tekstbestand heeft de volgende inhoud:

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

jacques : 11
sylvain : 12
xx : -1

xx: yy : zz
xx : yy

Het programma is als volgt:


using System;
using System.IO;

// syntaxis pg tekst bin logs
// er wordt een tekstbestand (tekst) gelezen en de inhoud ervan wordt opgeslagen in een binair bestand (bin)
// het tekstbestand bevat regels in de vorm naam : leeftijd die in een structuur van het type string, int worden opgeslagen
// (logs) is een tekstbestand met logbestanden

namespace Chap3 {
    class Program {
        static void Main(string[] arguments) {
            // er zijn 3 argumenten nodig
            if (arguments.Length != 3) {
                Console.WriteLine("syntaxe : pg texte binaire log");
                Environment.Exit(1);
            }//if

            // variabelen
            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;
            // tekstbestand lezen – binair bestand schrijven
            try {
                // het tekstbestand openen om te lezen
                input = new StreamReader(arguments[0]);
                // binair bestand openen om te schrijven
                output = new BinaryWriter(new FileStream(arguments[1], FileMode.Create, FileAccess.Write));
                // logbestand openen voor schrijven
                logs = new StreamWriter(arguments[2]);
                // verwerken van het tekstbestand
                while ((ligne = input.ReadLine()) != null) {
                    // nog een regel
                    numLigne++;
                    // lege regel?
                    if (ligne.Trim() == "") {
                        // wordt genegeerd
                        continue;
                    }
                    // een regel met naam: leeftijd
                    champs = ligne.Split(séparateurs);
                    // we hebben 2 velden nodig
                    if (champs.Length != 2) {
                        // de fout wordt gelogd
                        logs.WriteLine("La ligne n° [{0}] du fichier [{1}] a un nombre de champs incorrect", numLigne, arguments[0]);
                        // volgende regel
                        continue;
                    }//if
                    // het eerste veld mag niet leeg zijn
                    erreur = false;
                    nom = champs[0].Trim();
                    if (nom == "") {
                        // de fout wordt gelogd
                        logs.WriteLine("La ligne n° [{0}] du fichier [{1}] a un nom vide", numLigne, arguments[0]);
                        erreur = true;
                    }
                    // het tweede veld moet een geheel getal zijn >=0
                    if (!int.TryParse(champs[1],out age) || age<0) {
                        // de fout wordt gelogd
                        logs.WriteLine("La ligne n° [{0}] du fichier [{1}] a un âge [{2}] incorrect", numLigne, arguments[0], champs[1].Trim());
                        erreur = true;
                    }//if
                    // als er geen fout is, worden de gegevens naar het binaire bestand geschreven
                    if (!erreur) {
                        output.Write(nom);
                        output.Write(age);
                    }
                    // volgende regel
                }//while
            }catch(Exception e){
                Console.WriteLine("L'erreur suivante s'est produite : {0}", e.Message);
            } finally {
                // bestanden sluiten
                if(input!=null) input.Close();
                if(output!=null) output.Close();
                if(logs!=null) logs.Close();
            }
        }
    }
}

Laten we even stilstaan bij de bewerkingen met betrekking tot de klasse BinaryWriter:

  • regel 34: het object BinaryWriter wordt geopend door de bewerking

            output=new BinaryWriter(new FileStream(arguments[1],FileMode.Create,FileAccess.Write));

Het argument van de constructor moet een stream zijn. Hier gaat het om een stream die is opgebouwd uit een bestand (FileStream) waarvan we het volgende opgeven:

  • (vervolg)
    • de naam
    • de uit te voeren bewerking, hier FileMode.Create om het bestand aan te maken
    • het type toegang, hier FileAccess.Write voor schrijftoegang tot het bestand
  • regels 70-73: de schrijfbewerkingen
             // de gegevens worden naar het binaire bestand geschreven
            output.Write(nom);
            output.Write(age);

De klasse BinaryWriter beschikt over verschillende overladen methoden Write om de verschillende soorten eenvoudige gegevens te schrijven

  • regel 81: de bewerking om de stream te sluiten
        output.Close();

De drie argumenten van de methode Main worden aan het project doorgegeven (via de eigenschappen ervan) [1] en het te verwerken tekstbestand wordt in de map bin/Release geplaatst [2]:

Met het volgende bestand [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

zijn de resultaten van de uitvoering als volgt:

  • in [1], het binaire bestand [personnes1.bin] dat op deze manier is aangemaakt, evenals het logbestand [logs.txt]. Dit logbestand heeft de volgende inhoud:
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

De inhoud van het binaire bestand [personnes1.bin] wordt ons door het volgende programma verstrekt. Dit programma accepteert eveneens drie argumenten:

// syntaxis pg bin tekst logs
// een binair bestand (bin) wordt gelezen en de inhoud ervan wordt opgeslagen in een tekstbestand (tekst)
// het binaire bestand heeft een structuur met strings en integers
// het tekstbestand bevat regels in de vorm naam : leeftijd
// logs is een tekstbestand met logbestanden

We voeren dus de omgekeerde bewerking uit. We lezen een binair bestand in om een tekstbestand te maken. Als het resulterende tekstbestand identiek is aan het oorspronkelijke bestand, weten we dat de conversie tekst --> binair --> tekst goed is verlopen. De code is als volgt:


using System;
using System.IO;

// syntaxis pg bin tekst logs
// er wordt een binair bestand `bin` gelezen en de inhoud ervan wordt opgeslagen in een tekstbestand (`tekst`)
// het binaire bestand heeft een structuur van het type string, int
// het tekstbestand bevat regels in de vorm naam : leeftijd
// logs is een tekstbestand met logbestanden

namespace Chap3 {
    class Program2 {
        static void Main(string[] arguments) {
            // er zijn 3 argumenten nodig
            if (arguments.Length != 3) {
                Console.WriteLine("syntaxe : pg binaire texte log");
                Environment.Exit(1);
            }//if

            // variabelen
            string nom = null;
            int age = 0;
            int numPersonne = 1;
            BinaryReader input = null;
            StreamWriter output = null;
            StreamWriter logs = null;
            bool fini;
            // binair bestand lezen – tekstbestand schrijven
            try {
                // het binaire bestand openen om te lezen
                input = new BinaryReader(new FileStream(arguments[0], FileMode.Open, FileAccess.Read));
                // het tekstbestand openen om te schrijven
                output = new StreamWriter(arguments[1]);
                // logbestand openen om te schrijven
                logs = new StreamWriter(arguments[2]);
                // verwerking van het binaire bestand
                fini = false;
                while (!fini) {
                    try {
                        // naam lezen
                        nom = input.ReadString().Trim();
                        // lezen van leeftijd
                        age = input.ReadInt32();
                        // schrijven naar tekstbestand
                        output.WriteLine(nom + ":" + age);
                        // volgende persoon
                        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 {
                // bestanden sluiten
                if (input != null)
                    input.Close();
                if (output != null)
                    output.Close();
                if (logs != null)
                    logs.Close();
            }
        }
    }
}

Laten we even stilstaan bij de bewerkingen met betrekking tot de klasse BinaryReader:

  • regel 30: het object BinaryReader wordt geopend door de bewerking

            input=new BinaryReader(new FileStream(arguments[0],FileMode.Open,FileAccess.Read));

Het argument van de constructor moet een stream zijn. Hier is het een stream die is opgebouwd uit een bestand (FileStream) waarvan we het volgende opgeven:

  • (vervolg)
    • de naam
    • de uit te voeren bewerking, hier FileMode.Open om een bestaand bestand te openen
    • het type toegang, hier FileAccess.Read voor leestoegang tot het bestand
  • regels 40, 42: de leesbewerkingen
nom=input.ReadString().Trim();
age=input.ReadInt32();

De klasse BinaryReader beschikt over verschillende methoden ReadXX om de verschillende soorten eenvoudige gegevens te lezen

  • regel 60: de bewerking om de stream te sluiten
        input.Close();

Als we de twee programma’s achter elkaar uitvoeren, waarbij personnes1.txt wordt omgezet in personnes1.bin en vervolgens personnes1.bin in personnes2.txt2, krijgen we de volgende resultaten:

  • in [1] is het project zo geconfigureerd dat de tweede toepassing wordt uitgevoerd
  • in [2], de argumenten die aan Main zijn doorgegeven
  • in [3]: de bestanden die bij de uitvoering van de applicatie zijn gegenereerd.

De inhoud van [personnes2.txt] is als volgt:

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

5.7. Reguliere expressies

De klasse System.Text.RegularExpressions.Regex maakt het gebruik van reguliere expressies mogelijk. Hiermee kan het formaat van een tekenreeks worden getoetst. Zo kan worden gecontroleerd of een tekenreeks die een datum weergeeft, daadwerkelijk de indeling dd/mm/jj heeft. Hiervoor wordt een patroon gebruikt en wordt de tekenreeks met dit patroon vergeleken. In dit voorbeeld moeten j, m en a dus cijfers zijn. Het patroon voor een geldige datumnotatie is dan "\d\d/\d\d/\d\d", waarbij het symbool \d een cijfer aangeeft. De volgende symbolen kunnen in een patroon worden gebruikt:

Caractère
Beschrijving
\ 
Markeer het volgende teken als speciaal teken of letterlijk teken. Bijvoorbeeld: "n" staat voor het teken "n". "\n" staat voor een nieuwe regel. De reeks "\\" staat voor "\", terwijl "\(" staat voor "(".
^ 
Komt overeen met het begin van de invoer.
$ 
Komt overeen met het einde van de invoer.
* 
Komt nul of meerdere keren overeen met het voorgaande teken. Zo komt "zo*" overeen met "z" of met "zoo".
+ 
Komt één of meerdere keren overeen met het voorgaande teken. Zo komt "zo+" overeen met "zoo", maar niet met "z".
? 
Komt overeen met het voorgaande teken nul of één keer. Bijvoorbeeld: "a?ve?" komt overeen met "ve" in "lever".
.
Komt overeen met elk willekeurig teken, behalve het nieuwe-regel-teken.
(modèle) 
Zoekt naar modèle en slaat de overeenkomst op. De overeenkomende subreeks kan uit de verkregen verzameling Matches worden gehaald met behulp van Item [0]...[n]. Om overeenkomsten te vinden met tekens tussen haakjes ( ), gebruikt u "\(" of "\)".
x|y
Komt overeen met x of met y. Bijvoorbeeld: "z|foot" komt overeen met "z" of met "foot". "(z|f)oo" komt overeen met "zoo" of met "foo".
{n}
n is een niet-negatief geheel getal. Komt exact overeen met n maal het teken. Bijvoorbeeld: "o{2}" komt niet overeen met "o" in "Bob," maar met de eerste twee "o" in "fooooot".
{n,} 
n is een niet-negatief geheel getal. Komt overeen met ten minste n keer het teken. Bijvoorbeeld: "o{2,}" komt niet overeen met "o" in "Bob", maar met alle "o" in "fooooot". "o{1,}" is gelijk aan "o+" en "o{0,}" is gelijk aan "o*".
{n,m} 
m en n zijn niet-negatieve gehele getallen. Komt overeen met ten minste n en ten hoogste m keer het teken. Bijvoorbeeld: "o{1,3}" komt overeen met de eerste drie "o" in "foooooot" en "o{0,1}" staat gelijk aan "o?".
[xyz] 
Tekenset. Komt overeen met een van de opgegeven tekens. Bijvoorbeeld: "[abc]" komt overeen met "a" in "plat".
[^xyz] 
Negatieve tekenset. Komt overeen met elk teken dat niet is opgegeven. Bijvoorbeeld: "[^abc]" komt overeen met "p" in "plat".
[a-z] 
Tekenreeks. Komt overeen met elk teken in de opgegeven reeks. Bijvoorbeeld: "[a-z]" komt overeen met elk klein letterteken tussen "a" en "z".
[^m-z] 
Negatief tekenbereik. Komt overeen met elk teken dat niet in de opgegeven reeks voorkomt. Bijvoorbeeld: "[^m-z]" komt overeen met elk teken dat niet tussen "m" en "z" voorkomt.
\b 
Komt overeen met een grens die een woord vertegenwoordigt, met andere woorden, de positie tussen een woord en een spatie. Bijvoorbeeld: "er\b" komt overeen met "er" in "lever", maar niet met "er" in "verbe".
\B 
Komt overeen met een grens die geen woord vertegenwoordigt. "en*t\B" komt overeen met "ent" in "bien entendu".
\d 
Komt overeen met een teken dat een cijfer vertegenwoordigt. Komt overeen met [0-9].
\D 
Komt overeen met een teken dat geen cijfer vertegenwoordigt. Is gelijk aan [^0-9].
\f 
Komt overeen met een pagina-einde-teken.
\n 
Komt overeen met een regeleinde-teken.
\r 
Komt overeen met een carriage return-teken.
\s 
Komt overeen met alle witruimte, inclusief spatie, tab, pagina-einde, enz. Komt overeen met "[ \f\n\r\t\v]".
\S 
Komt overeen met elk niet-wit spatiekarakter. Komt overeen met "[^ \f\n\r\t\v]".
\t 
Komt overeen met een tabteken.
\v 
Komt overeen met een verticaal tabteken.
\w 
Komt overeen met elk teken dat een woord vertegenwoordigt en een onderstrepingsteken bevat. Komt overeen met "[A-Za-z0-9_]".
\W 
Komt overeen met elk teken dat geen woord vertegenwoordigt. Komt overeen met "[^A-Za-z0-9_]".
\num 
Komt overeen met num, waarbij num een positief geheel getal is. Verwijst naar opgeslagen toewijzingen. Bijvoorbeeld: „(.)\1“ komt overeen met twee opeenvolgende identieke tekens.
\n
Komt overeen met n, waarbij n een octale escape-waarde is. Octale escape-waarden moeten uit 1, 2 of 3 cijfers bestaan. Zo komen bijvoorbeeld zowel "\11" als "\011" overeen met een tabteken. "\0011" is gelijk aan "\001" & "1". Octale escape-waarden mogen niet hoger zijn dan 256. Als dat wel het geval is, worden alleen de eerste twee cijfers in de uitdrukking meegenomen. Hiermee kunnen de codes ASCII in reguliere expressies worden gebruikt.
\xn
Komt overeen met n, waarbij n een hexadecimale escape-waarde is. Hexadecimale escape-waarden moeten verplicht uit twee cijfers bestaan. Bijvoorbeeld: "\x41" komt overeen met "A". "\x041" is gelijk aan "\x04" & "1". Hiermee kunnen de codes ASCII in reguliere expressies worden gebruikt.

Een element in een sjabloon kan één of meerdere keren voorkomen. Laten we enkele voorbeelden bekijken rond het symbool \d, dat één cijfer vertegenwoordigt:

sjabloon
betekenis
\d
een cijfer
\d?
0 of 1 cijfer
\d*
0 of meer cijfers
\d+
1 of meer cijfers
\d{2}
2 cijfers
\d{3,}
minstens 3 cijfers
\d{5,7}
tussen 5 en 7 cijfers

Laten we nu eens kijken naar het patroon waarmee het verwachte formaat van een tekenreeks kan worden beschreven:

gezochte tekenreeks
model
een datum in de indeling dd/mm/jj
\d{2}/\d{2}/\d{2}
een tijd in de notatie hh:mm:ss
\d{2}:\d{2}:\d{2}
een geheel getal zonder teken
\d+
een reeks spaties, eventueel leeg
\s*
een geheel getal zonder teken dat voorafgegaan of gevolgd kan worden door spaties
\s*\d+\s*
een geheel getal dat een teken kan hebben en voorafgegaan of gevolgd kan worden door spaties
\s*[+|-]?\s*\d+\s*
een reëel getal zonder teken dat voorafgegaan of gevolgd kan worden door spaties
\s*\d+(.\d*)?\s*
een reëel getal dat een teken kan hebben en voorafgegaan of gevolgd kan worden door spaties
\s*[+|]?\s*\d+(.\d*)?\s*
een tekenreeks die het woord juste bevat
\bjuste\b
  

Je kunt aangeven waar in de tekenreeks naar het patroon wordt gezocht:

patroon
betekenis
^sjabloon
het patroon begint de tekenreeks
patroon$
het patroon beëindigt de reeks
^patroon$
het patroon begint en eindigt de reeks
patroon
het patroon wordt overal in de tekenreeks gezocht, te beginnen bij het begin ervan.
gezochte tekenreeks
patroon
een tekenreeks die eindigt met een uitroepteken
!$
een tekenreeks die eindigt op een punt
\.$
een tekenreeks die begint met de reeks //
^//
een tekenreeks die uit slechts één woord bestaat, eventueel gevolgd of voorafgegaan door spaties
^\s*\w+\s*$
een tekenreeks die bestaat uit twee woorden, eventueel voorafgegaan of gevolgd door spaties
^\s*\w+\s*\w+\s*$
een tekenreeks die het woord secret bevat
\bsecret\b

De deelverzamelingen van een patroon kunnen worden "opgehaald". Zo kan niet alleen worden gecontroleerd of een tekenreeks overeenkomt met een bepaald patroon, maar kunnen ook de elementen in die tekenreeks worden opgehaald die overeenkomen met de deelverzamelingen van het patroon die tussen haakjes zijn geplaatst. Als we dus een tekenreeks analyseren die een datum dd/mm/jj bevat en we bovendien de elementen dd, mm en jj van deze datum willen ophalen, gebruiken we het patroon (\d\d)/(\d\d)/(\d\d).

5.7.1. Controleren of een tekenreeks overeenkomt met een bepaald patroon

Een object van het type Regex wordt als volgt opgebouwd:

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

Zodra de reguliere expressie is opgebouwd, kan deze worden vergeleken met tekenreeksen met behulp van de methode IsMatch:

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

Hier is een voorbeeld:


using System;
using System.Text.RegularExpressions;

namespace Chap3 {
    class Program {
        static void Main(string[] args) {
            // een reguliere expressie als sjabloon
            string modèle1 = @"^\s*\d+\s*$";
            Regex regex1 = new Regex(modèle1);
            // een exemplaar vergelijken met het patroon
            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
        }

    }
}

en de uitvoerresultaten:

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

5.7.2. Alle voorkomens van een patroon in een tekenreeks vinden

Met de methode **Matches** kun je de elementen uit een tekenreeks ophalen die aan een patroon voldoen:

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

De klasse MatchCollection heeft een eigenschap Count die het aantal elementen in de verzameling aangeeft. Als résultats een object MatchCollection is, dan is résultats[i] het i-de element van deze verzameling en is het van het type Match. De klasse Match heeft verschillende eigenschappen, waaronder de volgende:

  • Value: de waarde van het object Match,, dus een element dat overeenkomt met het model
  • Index: de positie waar het element is gevonden in de doorzochte reeks

Laten we het volgende voorbeeld eens bekijken:


using System;
using System.Text.RegularExpressions;

namespace Chap3 {
    class Program2 {
        static void Main(string[] args) {
            // meerdere voorkomens van het patroon in het voorbeeld
            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
        }
    }
}
  • regel 8: het gezochte patroon is een reeks cijfers
  • regel 10: de tekenreeks waarin naar dit patroon wordt gezocht
  • regel 11: we halen alle elementen uit exemplaire3 die voldoen aan het patroon modèle2
  • regels 14-16: deze worden weergegeven

De resultaten van de uitvoering van het programma zijn als volgt:

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. Delen van een patroon ophalen

Deelverzamelingen van een patroon kunnen worden "opgehaald". Zo kan niet alleen worden gecontroleerd of een tekenreeks overeenkomt met een bepaald patroon, maar kunnen ook de elementen uit die tekenreeks worden opgehaald die overeenkomen met de deelverzamelingen van het patroon die tussen haakjes zijn geplaatst. Als we dus een tekenreeks analyseren die een datum dd/mm/jj bevat en we willen bovendien de elementen dd, mm, jj van deze datum ophalen, dan gebruiken we het patroon (\d\d)/(\d\d)/(\d\d).

Laten we het volgende voorbeeld eens bekijken:


using System;
using System.Text.RegularExpressions;

namespace Chap3 {
    class Program3 {
        static void Main(string[] args) {
            // elementen in het sjabloon vastleggen
            string modèle3 = @"(\d\d):(\d\d):(\d\d)";
            Regex regex3 = new Regex(modèle3);
            string exemplaire4 = "Il est 18:05:49";
            // sjablooncontrole
            Match résultat = regex3.Match(exemplaire4);
            if (résultat.Success) {
                // het exemplaar komt overeen met het sjabloon
                Console.WriteLine("L'exemplaire [{0}] correspond au modèle [{1}]",exemplaire4,modèle3);
                // de haakjesgroepen worden weergegeven
                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 {
                // het exemplaar komt niet overeen met het sjabloon
                Console.WriteLine("L'exemplaire[{0}] ne correspond pas au modèle [{1}]", exemplaire4, modèle3);
            }
        }
    }
}

De uitvoering van dit programma levert de volgende resultaten op:

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

De nieuwigheid zit in de regels 12-19:

  • regel 12: de tekenreeks exemplaire4 wordt vergeleken met het sjabloon regex3 via de methode Match. Deze methode retourneert een object Match dat al eerder is geïntroduceerd. We gebruiken hier twee nieuwe eigenschappen van deze klasse:
  • Success (regel 13): geeft aan of er een overeenkomst is gevonden
  • Groups (regels 17, 18): verzameling waarin
    • Groups[0] overeenkomt met het deel van de tekenreeks dat overeenkomt met het patroon
    • Groups[i] (i>=1) overeenkomt met de groep haakjes nr. i

Als résultat van het type Match is, dan is résultats.Groups van het type GroupCollection en is résultats.Groups[i] van het type Group. De klasse Group heeft twee eigenschappen die we hier gebruiken:

  • Value (regel 18): de waarde van het object Group, dat het element is dat overeenkomt met de inhoud van een haakje
  • Index (regel 18): de positie waar het element is gevonden in de doorzochte tekenreeks

5.7.4. Een oefenprogramma

Het vinden van de reguliere expressie waarmee kan worden gecontroleerd of een tekenreeks wel degelijk overeenkomt met een bepaald patroon, is soms een echte uitdaging. Met het volgende programma kun je oefenen. Het vraagt om een patroon en een tekenreeks en geeft aan of de tekenreeks al dan niet overeenkomt met het patroon.


using System;
using System.Text.RegularExpressions;

namespace Chap3 {
    class Program4 {
        static void Main(string[] args) {
            // gegevens
            string modèle, chaine;
            Regex regex = null;
            MatchCollection résultats;
            // de gebruiker wordt gevraagd welke sjablonen en exemplaren met dit exemplaar moeten worden vergeleken
            while (true) {
                // er wordt gevraagd om het sjabloon
                Console.Write("Tapez le modèle à tester ou rien pour arrêter :");
                modèle = Console.In.ReadLine();
                // klaar?
                if (modèle.Trim() == "")
                    break;
                // we maken de reguliere expressie
                try {
                    regex = new Regex(modèle);
                } catch (Exception ex) {
                    Console.WriteLine("Erreur : " + ex.Message);
                    continue;
                }
                // de gebruiker wordt gevraagd welke exemplaren met het sjabloon moeten worden vergeleken
                while (true) {
                    Console.Write("Tapez la chaîne à comparer au modèle [{0}] ou rien pour arrêter :", modèle);
                    chaine = Console.ReadLine();
                    // klaar?
                    if (chaine.Trim() == "")
                        break;
                    // we voeren de vergelijking uit
                    résultats = regex.Matches(chaine);
                    // geslaagd?
                    if (résultats.Count == 0) {
                        Console.WriteLine("Je n'ai pas trouvé de correspondances");
                        continue;
                    }//if
                    // worden de elementen weergegeven die overeenkomen met het model
                    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);
                        // van de subelementen
                        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 volgt een voorbeeld van de uitvoering:

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. De methode Split

We zijn deze methode al tegengekomen in de les String:


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

Met de methode Split van de klasse Regex kunnen we het scheidingsteken op basis van een sjabloon specificeren:


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.

Stel bijvoorbeeld dat we in een tekstbestand regels hebben in de vorm veld1, veld2, ..., veldn. De velden worden gescheiden door een komma, maar deze kan voorafgegaan of gevolgd worden door spaties. De methode Split van de klasse string is in dat geval niet geschikt. De methode RegEx biedt de oplossing. Als ligne de gelezen regel is, kunnen de velden worden verkregen via

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

zoals het volgende voorbeeld laat zien:


using System;
using System.Text.RegularExpressions;

namespace Chap3 {
    class Program5 {
        static void Main(string[] args) {
            // een regel
            string ligne = "abc  , def  , ghi";
            // een sjabloon
            Regex modèle = new Regex(@"\s*,\s*");
            // uitsplitsing van een regel in velden
            string[] champs = modèle.Split(ligne);
            // weergave
            for (int i = 0; i < champs.Length; i++) {
                Console.WriteLine("champs[{0}]=[{1}]", i, champs[i]);
            }
        }
    }
}

De uitvoerresultaten:

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

5.8. Voorbeeldtoepassing - V3

We nemen de toepassing die in paragraaf 3.6 (versie 1) en 4.10 (versie 2) is besproken opnieuw onder de loep.

In de laatst besproken versie vond de belastingberekening plaats in de abstracte klasse AbstractImpot:


namespace Chap2 {
    abstract class AbstractImpot : IImpot {

        // de belastingschijven die nodig zijn voor de berekening van de belasting
        // zijn afkomstig van een externe bron

        protected TrancheImpot[] tranchesImpot;

        // berekening van de belasting
        public int calculer(bool marié, int nbEnfants, int salaire) {
            // berekening van het aantal aandelen
            decimal nbParts;
            if (marié) nbParts = (decimal)nbEnfants / 2 + 2;
            else nbParts = (decimal)nbEnfants / 2 + 1;
            if (nbEnfants >= 3) nbParts += 0.5M;
            // berekening van het belastbaar inkomen en de gezinsquotiënt
            decimal revenu = 0.72M * salaire;
            decimal QF = revenu / nbParts;
            // berekening van de belasting
            tranchesImpot[tranchesImpot.Length - 1].Limite = QF + 1;
            int i = 0;
            while (QF > tranchesImpot[i].Limite) i++;
            // resultaat weergeven
            return (int)(revenu * tranchesImpot[i].CoeffR - nbParts * tranchesImpot[i].CoeffN);
        }//berekenen
    }//klasse

}

De methode calculer op regel 38 maakt gebruik van de tabel tranchesImpot op regel 35, een tabel die niet is geïnitialiseerd door de klasse AbstractImpot. Daarom is deze methode abstract en moet deze worden afgeleid om bruikbaar te zijn. Deze initialisatie werd uitgevoerd door de afgeleide klasse HardwiredImpot:


using System;

namespace Chap2 {
    class HardwiredImpot : AbstractImpot {

        // tabel met gegevens die nodig zijn voor de berekening van de belasting
        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() {
                // opstellen van de belastingschijven
            tranchesImpot = new TrancheImpot[limites.Length];
                // invullen
            for (int i = 0; i < tranchesImpot.Length; i++) {
                tranchesImpot[i] = new TrancheImpot { Limite = limites[i], CoeffR = coeffR[i], CoeffN = coeffN[i] };
                }
        }
    }// klasse
}// naamruimte

Hierboven werden de gegevens die nodig zijn voor de berekening van de belasting „hard“ in de codeklasse vastgelegd. In de nieuwe versie van het voorbeeld worden ze in een tekstbestand geplaatst:

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

Aangezien het verwerken van dit bestand uitzonderingen kan veroorzaken, maken we een speciale klasse aan om deze af te handelen:


using System;

namespace Chap3 {
    class FileImpotException : Exception {
        // foutcodes
        [Flags]
        public enum CodeErreurs { Acces = 1, Ligne = 2, Champ1 = 4, Champ2 = 8, Champ3 = 16 };
    
        // foutcode
        public CodeErreurs Code { get; set; }

        // constructors
        public FileImpotException() {
        }
        public FileImpotException(string message)
            : base(message) {
        }
        public FileImpotException(string message, Exception e)
            : base(message,e) {
        }
    }
}
  • regel 4: de klasse FileImpotException is afgeleid van de klasse Exception. Deze klasse wordt gebruikt om alle fouten op te slaan die optreden tijdens het verwerken van het tekstbestand met gegevens.
  • regel 7: een opsomming met foutcodes:
    • Acces: fout bij het openen van het tekstbestand met gegevens
    • Ligne: regel bevat niet de drie verwachte velden
    • Champ1: veld nr. 1 is onjuist
    • Champ2: veld nr. 2 is onjuist
    • Champ3: veld nr. 3 is onjuist

Sommige van deze fouten kunnen gecombineerd voorkomen (Champ1, Champ2, Champ3). Daarom is de opsomming CodeErreurs voorzien van het attribuut [Flags], wat inhoudt dat de verschillende waarden van de opsomming machten van 2 moeten zijn. Een fout in de velden 1 en 2 leidt dan tot de foutcode Champ1 | Champ2.

  • regel 10: de automatische eigenschap Code slaat de foutcode op.
  • regel 15: een constructor waarmee een object FileImpotException kan worden aangemaakt door er een foutmelding als parameter aan door te geven.
  • regel 18: een constructor waarmee een object van het type FileImpotException kan worden aangemaakt door als parameters een foutmelding en de uitzondering die de fout heeft veroorzaakt door te geven.

De klasse die de array tranchesImpot van de klasse AbstractImpot initialiseert, ziet er nu als volgt uit:


using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;

namespace Chap3 {
    class FileImpot : AbstractImpot {

        public FileImpot(string fileName) {
            // gegevens
            List<TrancheImpot> listTranchesImpot = new List<TrancheImpot>();
            int numLigne = 1;
            // uitzondering
            FileImpotException fe = null;
            // inhoud van het bestand fileName regel voor regel lezen
            Regex pattern = new Regex(@"s*:\s*");
            // in eerste instantie geen fout
            FileImpotException.CodeErreurs code = 0;
            try {
                using (StreamReader input = new StreamReader(fileName)) {
                    while (!input.EndOfStream && code == 0) {
                        // huidige regel
                        string ligne = input.ReadLine().Trim();
                        // lege regels worden genegeerd
                        if (ligne == "") continue;
                        // regel opgesplitst in drie velden, gescheiden door:
                        string[] champsLigne = pattern.Split(ligne);
                        // zijn er 3 velden?
                        if (champsLigne.Length != 3) {
                            code = FileImpotException.CodeErreurs.Ligne;
                        }
                        // conversies van de 3 velden
                        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; ;
                        }
                        // fout?
                        if (code != 0) {
                            // de fout wordt genoteerd
                            fe = new FileImpotException(String.Format("Ligne n° {0} incorrecte", numLigne)) { Code = code };
                        } else {
                            // de nieuwe belastingschijf wordt opgeslagen
                            listTranchesImpot.Add(new TrancheImpot() { Limite = limite, CoeffR = coeffR, CoeffN = coeffN });
                            // volgende regel
                            numLigne++;
                        }
                    }
                }
                // de lijst listImpot wordt overgebracht naar de tabel tranchesImpot
                if (code == 0) {
                    // de lijst listImpot wordt overgebracht naar de tabel tranchesImpot
                    tranchesImpot = listTranchesImpot.ToArray();
                }
            } catch (Exception e) {
                // er wordt een fout genoteerd
                fe= new FileImpotException(String.Format("Erreur lors de la lecture du fichier {0}", fileName), e) { Code = FileImpotException.CodeErreurs.Acces };
            }
            // moet de fout worden gemeld?
            if (fe != null) throw fe;
        }
    }
}
  • regel 7: de klasse FileImpot is afgeleid van de klasse AbstractImpot, net zoals in versie 2 de klasse HardwiredImpot dat was.
  • regel 9: de constructor van de klasse FileImpot heeft als taak het veld trancheImpot van de basisklasse AbstractImpot te initialiseren. Als parameter wordt de naam van het tekstbestand met de gegevens doorgegeven.
  • regel 11: het veld tranchesImpot van de basisklasse AbstractImpot is een array die moet worden gevuld met de gegevens uit het tekstbestand filename dat als parameter wordt doorgegeven. Het lezen van een tekstbestand gebeurt sequentieel. Het aantal regels is pas bekend nadat het gehele bestand is ingelezen. Daarom kan de array tranchesImpot niet vooraf worden gedimensioneerd. De gegevens worden tijdelijk opgeslagen in de generieke lijst listTranchesImpot.

Ter herinnering: het type TrancheImpot is een structuur:


namespace Chap3 {
    // een belastingschijf
    struct TrancheImpot {
        public decimal Limite { get; set; }
        public decimal CoeffR { get; set; }
        public decimal CoeffN { get; set; }
    }
}
  • regel 14: fe van het type FileImpotException dient om een eventuele fout bij de verwerking van het tekstbestand in te kapselen.
  • regel 16: de reguliere uitdrukking voor het veldscheidingsteken in een regel champ1:champ2:champ3 van het tekstbestand. De velden worden gescheiden door het teken :, voorafgegaan en gevolgd door een willekeurig aantal spaties.
  • regel 18: de foutcode in geval van een fout
  • regel 20: verwerking van het tekstbestand met een StreamReader
  • regel 21: er wordt een lus doorlopen zolang er nog een regel te lezen is en er geen fout is opgetreden
  • regel 27: de gelezen regel wordt in velden opgesplitst met behulp van de reguliere expressie uit regel 16
  • regels 29-31: er wordt gecontroleerd of de regel inderdaad drie velden bevat – een eventuele fout wordt genoteerd
  • regels 33-38: de drie tekenreeksen worden omgezet in drie decimale getallen – eventuele fouten worden genoteerd
  • regels 40-43: als er een fout is opgetreden, wordt een uitzondering van het type FileImpotException gegenereerd.
  • regels 44-47: als er geen fout is opgetreden, gaat men verder met het lezen van de volgende regel van het tekstbestand, nadat de gegevens uit de huidige regel zijn opgeslagen.
  • regels 52-55: bij het verlaten van de uitgang while worden de gegevens uit de generieke lijst listTranchesImpot gekopieerd naar de tabel tranchesImpot van de basisklasse AbstractImpot. We herinneren eraan dat dit het doel van de constructor was.
  • regels 56-59: afhandeling van een eventuele uitzondering. Deze wordt ingekapseld in een object van het type FileImpotException.
  • regel 61: als de uitzondering fe uit regel 18 is geïnitialiseerd, wordt deze afgevuurd.

Het volledige C#-project ziet er als volgt uit:

  • in [1]: het volledige project
  • in [2,3]: de eigenschappen van het bestand [DataImpot.txt] [2]. De eigenschap [Copy to Output Directory] [3] wordt ingesteld op always. Dit zorgt ervoor dat het bestand [DataImpot.txt] bij elke uitvoering wordt gekopieerd naar de map bin/Release (Release-modus) of bin/Debug (Debug-modus). Daar wordt het door het uitvoerbare bestand opgezocht.
  • In [4]: hetzelfde gebeurt met het bestand [DataImpotInvalide.txt].

De inhoud van [DataImpot.txt] is als volgt:

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

De inhoud van [DataImpotInvalide.txt] is als volgt:

a:b:c

Het testprogramma [Program.cs] is niet gewijzigd: het is dat van versie 2, paragraaf 4.10, met het volgende verschil:


using System;

namespace Chap3 {
    class Program {
        static void Main() {
...
             // aanmaken van een object IImpot
            IImpot impot = null;
            try {
                // aanmaken van een object IImpot
                impot = new FileImpot("DataImpot.txt");
            } catch (FileImpotException e) {
                // foutmelding
                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);
                // programma stoppen
                Environment.Exit(1);
            }

            // oneindige lus
            while (true) {
...
            }//while
        }
    }
}
  • regel 8: object impot van het type van de interface IImpot
  • regel 11: instantiëring van het object impot met een object van het type FileImpot. Dit kan een uitzondering genereren die wordt afgehandeld door de try/catch-constructie op de regels 9, 12 en 18.

Hier volgen enkele uitvoervoorbeelden:

Met het bestand [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 :

Met een niet-bestaand bestand [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'.]

Met het bestand [DataImpotInvalide.txt]

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