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:
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
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:
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:
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);
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. | |
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);
array van tekenreeksen | |
een tekenreeks die als veldscheidingsteken zal dienen | |
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:
| |
|
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:
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:
| |
| |
| |
| |
|
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:
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
Methoden
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:
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:

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:
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
Methoden
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:
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:
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<TKey,TValue>*** zijn de volgende:
Constructors
Eigenschappen
Methoden
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:
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
Eigenschappen
Methoden
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:
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:
Als we op regel 15 de bestandsnaam xx.txt invoeren, krijgen we de volgende resultaten:
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
Eigenschappen
Methoden
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:
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:
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 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
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]:
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:
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
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
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:
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:
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. | |
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 "\)". | |
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 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 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*". | |
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?". | |
Tekenset. Komt overeen met een van de opgegeven tekens. Bijvoorbeeld: "[abc]" komt overeen met "a" in "plat". | |
Negatieve tekenset. Komt overeen met elk teken dat niet is opgegeven. Bijvoorbeeld: "[^abc]" komt overeen met "p" in "plat". | |
Tekenreeks. Komt overeen met elk teken in de opgegeven reeks. Bijvoorbeeld: "[a-z]" komt overeen met elk klein letterteken tussen "a" en "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. | |
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". | |
Komt overeen met een grens die geen woord vertegenwoordigt. "en*t\B" komt overeen met "ent" in "bien entendu". | |
Komt overeen met een teken dat een cijfer vertegenwoordigt. Komt overeen met [0-9]. | |
Komt overeen met een teken dat geen cijfer vertegenwoordigt. Is gelijk aan [^0-9]. | |
Komt overeen met een pagina-einde-teken. | |
Komt overeen met een regeleinde-teken. | |
Komt overeen met een carriage return-teken. | |
Komt overeen met alle witruimte, inclusief spatie, tab, pagina-einde, enz. Komt overeen met "[ \f\n\r\t\v]". | |
Komt overeen met elk niet-wit spatiekarakter. Komt overeen met "[^ \f\n\r\t\v]". | |
Komt overeen met een tabteken. | |
Komt overeen met een verticaal tabteken. | |
Komt overeen met elk teken dat een woord vertegenwoordigt en een onderstrepingsteken bevat. Komt overeen met "[A-Za-z0-9_]". | |
Komt overeen met elk teken dat geen woord vertegenwoordigt. Komt overeen met "[^A-Za-z0-9_]". | |
Komt overeen met num, waarbij num een positief geheel getal is. Verwijst naar opgeslagen toewijzingen. Bijvoorbeeld: „(.)\1“ komt overeen met twee opeenvolgende identieke tekens. | |
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. | |
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:
Zodra de reguliere expressie is opgebouwd, kan deze worden vergeleken met tekenreeksen met behulp van de methode IsMatch:
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:
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:
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:
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:
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:
5.7.5. De methode Split
We zijn deze methode al tegengekomen in de les String:
|
Met de methode Split van de klasse Regex kunnen we het scheidingsteken op basis van een sjabloon specificeren:
|
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
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:
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:
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]
Met een niet-bestaand bestand [xx]
Met het bestand [DataImpotInvalide.txt]






























