Skip to content

5. Commonly used .NET classes

We present ici some frequently used classes from the .NET platform. Before that, we'll show you how to obtain information on the hundreds of classes available. This help is indispensable for even the most experienced C# developer. The quality of help (easy access, comprehensible organization, relevance of information, etc.) can make or break a development environment.

5.1. Find help on .NET classes

We give ici some hints on how to find help with Visual Studio.NET

5.1.1. Help/Contents

  • in [1], take the option Help/Contents menu.
  • in [2], take option Visual C# Express Edition
  • in [3], the C# help tree
  • in [4], another useful option is the .NET Framework, which gives access to all .NET framework classes.

Let's take a look at the chapter headings in C# Help:

  • [1]: an overview of C#
  • [2]: a series of examples on certain aspects of C#
  • [3] : a C# course - could replace the present document..
  • [4]: to find out more about C#
  • [5]: useful for C++ or Java developers. Helps avoid a few pitfalls.
  • [6]: when looking for examples, you can start there.
  • [7]: what you need to know to create graphical user interfaces
  • [8]: to get the most out of IDE Visual Studio Express
  • [9] : SQL Server Express 2005 is a high-quality SGBD distributed free of charge. It will be used in this course.

C# help is only part of what the developer needs. The other part is help with the hundreds of classes in the .NET framework that will make his work easier.

  • [1]: select help on the .NET framework
  • [2]: help is in the .NET Framework SDK
  • [3]: the branch .NET Framework Class Library presents all .NET classes according to the namespace to which they belong
  • [4]: the namespace System which was most often used in the examples in the previous chapters
  • [5]: in the namespace System, an example, ici the structure DateTime
  • [6]: help on structure DateTime

5.1.2. Help/Index/Search

The help provided by MSDN is immense and you may not know where to look. You can then use the help index:

  • in [1], use option [Help/Index] if the help window is not already open, otherwise use [2] in an existing help window.
  • in [3], specify the field in which the research is to be carried out
  • in [4], specify what you are looking for, ici a class
  • in [5], the answer

Another way to search for help is to use the search help :

  • in [1], use option [Help/Search] if the help window is not already open, otherwise use [2] in an existing help window.
  • in [3], specify what you are looking for
  • in [4], filter search fields
  • in [5], the answer in the form of different themes where the searched text has been found.

5.2. Character strings

5.2.1. The System.String class

The class System.String is identical to the simple type string. It has many properties and methods. Here are just a few of them:


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

An important point to note: when a method renders a string, the string is a different chain of the chain to which the method has been applied. For example, S1.Trim() renders a string S2, and S1 and S2 are two different chains.

A C string can be considered as an array of characters. So

  • C[i] is character i of C
  • C.Length is the number of characters in C

Consider the following example:


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() + "]");
         }//Main
 
        public static void affiche(string msg) {
             // poster msg
            Console.WriteLine(msg);
         }//poster
     }//class
}//namespace

Execution gives the following results:

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]

Let's look at a new example:


using System;
 
namespace Chap3 {
    class Program {
        static void Main(string[] args) {
             // the line to be analyzed
            string ligne = "un:deux::trois:";
             // field separators
            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]);
            }
             // join
            Console.WriteLine("join=[" + System.String.Join(":", champs) + "]");
        }
    }
}

and performance results :

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

The method Split class String is used to put elements of a character string into an array. The definition of the Split used ici is as follows:


    public string[] Split(char[] separator);
separator
array of characters. These characters represent the characters used to separate the fields of the string. So if the string is "field1, field2, field3" we can use separator=new char[] {','}. If the separator is a series of spaces, use separator=null.
résultat
string array where each element of the array is a string field.

The method Join is a static method of the String :


    public static string Join(string separator, string[] value);
value
string array
separator
a character string to be used as a field separator
résultat
a character string formed from the concatenation of array elements value separated by the chain separator.

5.2.2. The System.Text.StringBuilder class

Earlier, we said that the methods of the String that applied to a string of characters S1 made another chain S2. The class System.Text.StringBuilder allows you to manipulate S1 without having to create an S2 chain. This improves performance by avoiding the multiplication of chains with a very limited lifespan.

The class admits various 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.

An object StringBuilder works with blocks of capacity characters to store the underlying string. Default setting capacity is 16. The 3rd constructor above is used to specify the block capacity. The number of capacity characters required to store an S string is automatically adjusted by the StringBuilder. There are constructors for setting the maximum number of characters in an object StringBuilder. By default, this maximum capacity is 2,147,483,647.

Here's an example to illustrate the notion of capacity :


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);
            }
        }
    }
}
  • line 7: object creation StringBuilder with a block size of 16 characters
  • line 8: str.Length is the current number of characters in the string str. str.Capacity is the number of characters that can be stored in the string str before reallocating a new block.
  • line 10: str.Append(String S) concatenates the string S type String on the line str type StringBuilder.
  • line 14: object creation StringBuilder with 10-character block capacity

Execution result:

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

These results show that the class follows its own algorithm for allocating new blocks when its capacity is insufficient:

  • lines 4-5: 16-character capacity increase
  • lines 8-9: capacity increased from 16 to 32 characters.

Here are some of the class methods:


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.

Here's an example:


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

and its results:

XYZTstabCD

5.3. Paintings

Arrays are derived from the Array :

The class Array has various methods for sorting an array, searching for an element in an array, resizing an array, etc. We present some of the properties and methods of this class. They are almost all overloaded, c.a.d. and exist in different variants. Every array inherits them.

Properties

public int Length {get;}
nombre total d'array elements, regardless of the number of dimensions
public int Rank {get;}
nombre total de dimensions du tableau

Methods

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'elements of dimension no. i in the table
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'is not found.
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 4.8.

The following program illustrates the use of certain methods of the Array :


using System;
 
namespace Chap3 {
    class Program {
         // search type
        enum TypeRecherche { linéaire, dichotomique };
 
         // main method
        static void Main(string[] args) {
             // reading table elements typed on the keyboard
            double[] éléments;
            Saisie(out éléments);
             // unsorted table display
            Affiche("Tableau non trié", éléments);
             // Linear search in unsorted table
            Recherche(éléments, TypeRecherche.linéaire);
             // table sorting
            Array.Sort(éléments);
             // sorted table display
            Affiche("Tableau trié", éléments);
             // Dichotomous search in sorted table
            Recherche(éléments, TypeRecherche.dichotomique);
        }
 
         // entering values for the elements table
         // elements: reference on table created by the
        static void Saisie(out double[] éléments) {
            bool terminé = false;
            string réponse;
            bool erreur;
            double élément = 0;
            int i = 0;
             // initially, the painting does not exist
            éléments = null;
             // table element input loop
            while (!terminé) {
                 // question
                Console.Write("Elément (réel) " + i + " du tableau (rien pour terminer) : ");
                 // reading the answer
                réponse = Console.ReadLine().Trim();
                 // end of input if string empty
                if (réponse.Equals(""))
                    break;
                 // input verification
                try {
                    élément = Double.Parse(réponse);
                    erreur = false;
                } catch {
                    Console.Error.WriteLine("Saisie incorrecte, recommencez");
                    erreur = true;
                 }//try-catch
                 // if no error
                if (!erreur) {
                     // one more element in the table
                    i += 1;
                     // resize table to accommodate new element
                    Array.Resize(ref éléments, i);
                     // insert new element
                    éléments[i - 1] = élément;
                }
             }//while
        }
 
         // generic method for displaying a picture's elements
        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);
            }
        }
 
        // recherche d'an element in the array
         // elements: array of real
         // TypeRecherche: dichotomous or linear
        static void Recherche(double[] éléments, TypeRecherche type) {
             // Search
            bool terminé = false;
            string réponse = null;
            double élément = 0;
            bool erreur = false;
            int i = 0;
            while (!terminé) {
                 // question
                Console.WriteLine("Elément cherché (rien pour arrêter) : ");
                 // reading-checking response
                réponse = Console.ReadLine().Trim();
                 // finished?
                if (réponse.Equals(""))
                    break;
                 // check
                try {
                    élément = Double.Parse(réponse);
                    erreur = false;
                } catch {
                    Console.WriteLine("Erreur, recommencez...");
                    erreur = true;
                 }//try-catch
                 // if no error
                if (!erreur) {
                    // on cherche l'element in the table
                    if (type == TypeRecherche.dichotomique)
                         // dichotomous search
                        i = Array.BinarySearch(éléments, élément);
                    else
                         // linear search
                        i = Array.IndexOf(éléments, élément);
                     // Display response
                    if (i >= 0)
                        Console.WriteLine("Trouvé en position " + i);
                    else
                        Console.WriteLine("Pas dans le tableau");
                 }//if
             }//while
        }
    }
}
  • lines 27-62: the method Input captures the elements of an array elements typed on the keyboard. As we can't size the array a priori (we don't know its final size), we have to resize it for each new element (line 57). A more efficient algorithm would have been to allocate space to the array in groups of N elements. However, an array is not designed to be resized. This case is better handled with a list (ArrayList, List<T>).
  • lines 75-113: the method Search to search the table elements, an element typed on the keyboard. The search mode differs depending on whether the table is sorted or unsorted. For an unsorted array, a linear search is performed using the IndexOf of line 106. For a sorted table, a dichotomous search is performed using the BinarySearch on line 103.
  • line 18: the table is sorted elements. We use ici, a variant of Spell which has just one parameter: the array to be sorted. The order relation used to compare the elements of the array is then the implicit one of these elements. in the case of Ici, the elements are numerical. The natural order of numbers is used.

The screen results are as follows:

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. Generic collections

In addition to arrays, there are various classes for storing collections of elements. There are generic versions in the namespace System.Collections.Generic and non-generic versions in System.Collections. We present two frequently used generic collections: the list and the dictionary.

The list of generic collections is as follows:

Image

5.4.1. The generic List<T> class

The class System.Collections.Generic.List<T> allows you to implement collections of objects of type T whose size varies during program execution. An object of type List<T> can be manipulated almost like an array. Thus, element i of a list l is denoted l[i].

There is also a non-generic list type: ArrayList capable of storing references to any object. ArrayList is functionally equivalent to List<Object>. An object ArrayList looks like this:

Above, elements 0, 1 and i in the list point to objects of different types. An object must first be created before adding its reference to the list ArrayList. Although a ArrayList stores object references, it is possible to store numbers. This is done through a mechanism called Boxing : the number is encapsulated in an O object of type Object and the O reference is stored in the list. This mechanism is transparent to the developer. You can write :

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

This will produce the following result:

Above, the number 4 has been encapsulated in an O object and the O reference is stored in the list. To retrieve it, write :


            int i = (int)liste[0];

The operation Object -> int is called Unboxing. If a list is composed entirely of int, declare it as List<int> improves performance. In fact, numbers of type int are then stored in the list itself and not in Object outside the list. Boxing / Unboxing operations no longer take place.

For an object List<T> or T is a class, the list again stores references to objects of type T :

Here are some of the properties and methods of generic lists:

Properties

public int Count {get;}
nombre d'list items
public int Capacity {get;}
nombre d'elements the list can contain before being resized. This
redimensionnement se fait automatiquement. Cette notion de capacité de liste
est analogue à celle de capacité décrite pour la classe StringBuilder paragraphe 5.2.2.

Methods

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

Let's go back to the previous example with an object of type Array and now treat it with an object of type List<T>. Because the list is an object close to the table, the code changes very little. We present only the most notable changes:


using System;
using System.Collections.Generic;
 
namespace Chap3 {
    class Program {
         // search type
        enum TypeRecherche { linéaire, dichotomique };
 
         // main method
        static void Main(string[] args) {
             // play list items typed on keyboard
            List<double> éléments;
            Saisie(out éléments);
             // number of elements
            Console.WriteLine("La liste a {0} éléments et une capacité de {1} éléments", éléments.Count, éléments.Capacity);
             // display unsorted list
            Affiche("Liste non triée", éléments);
             // Linear search in unsorted list
            Recherche(éléments, TypeRecherche.linéaire);
             // list sorting
            éléments.Sort();
             // sorted list display
            Affiche("Liste triée", éléments);
             // Dichotomous search in sorted list
            Recherche(éléments, TypeRecherche.dichotomique);
        }
 
         // enter values for the items list
         // elements: reference to the list created by the
        static void Saisie(out List<double> éléments) {
...
             // initially, the list is empty
            éléments = new List<double>();
             // list item entry loop
            while (!terminé) {
...
                 // if no error
                if (!erreur) {
                     // one more item in the list
                    éléments.Add(élément);
                }
             }//while
        }
 
         // generic method for displaying the elements of an enumerable object
        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);
            }
        }
 
         // search for an item in a list
         // elements: list of real numbers
         // TypeRecherche: dichotomous or linear
        static void Recherche(List<double> éléments, TypeRecherche type) {
...
            while (!terminé) {
...
                 // if no error
                if (!erreur) {
                     // search for the element in the list
                    if (type == TypeRecherche.dichotomique)
                         // dichotomous search
                        i = éléments.BinarySearch(élément);
                    else
                         // linear search
                        i = éléments.IndexOf(élément);
                     // Display response
...
                 }//if
             }//while
        }
    }
}
  • lines 46-51: the generic method Poster<T> has two parameters :
  • the 1st parameter is a text to be written
  • the 2nd parameter is an object implementing the generic interface IEnumerable<T> :
1
2
3
4
public interface IEnumerable<T>{
    IEnumerator GetEnumerator();
    IEnumerator<T> GetEnumerator();
}

The structure foreach( T element in elements) of line 48, is valid for any object elements implementing the IEnumerable. The tables (Array) and lists (List<T>) implement the IEnumerable<T>. The Poster is equally suited to displaying tables and lists.

The results of program execution are the same as in the example using the Array.

5.4.2. The Dictionary<TKey,TValue> class

The class System.Collections.Generic.Dictionary<TKey,TValue> is used to implement a dictionary. A dictionary can be thought of as an array with two columns:

key
value
key1
value1
key2
value2
..
...

In the classroom Dictionary<TKey,TValue> keys are Tkey, type values TValue. Keys are unique, c.a.d. that no two keys can be identical. Such a dictionary could look like this if the types TKey and TValue designated classes:

The value associated with the key C of a dictionary D is given by the notation D[C]. This value is readable and writable. Thus, we can write :

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

If the key c does not exist in the dictionary D, rating D[c] launches an exception.

The main methods and properties of the Dictionary<TKey,TValue> are as follows:

Manufacturers

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

Properties

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.

Methods

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)

Consider the following example program:


using System;
using System.Collections.Generic;
 
namespace Chap3 {
    class Program {
        static void Main(string[] args) {
            // creation of a <string,int> dictionary
            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
            // number of elements in the dictionary
            Console.WriteLine("Le dictionnaire a " + dico.Count + " éléments");
             // kEY LIST
            Affiche("[Liste des clés]",dico.Keys);
            // list of values
            Affiche("[Liste des valeurs]", dico.Values);
            // list of keys & values
            Console.WriteLine("[Liste des clés & valeurs]");
            foreach (string clé in dico.Keys) {
                Console.WriteLine("clé=" + clé + " valeur=" + dico[clé]);
            }
            // delete the "paul" key
            Console.WriteLine("[Suppression d'une clé]");
            dico.Remove("paul");
            // list of keys & values
            Console.WriteLine("[Liste des clés & valeurs]");
            foreach (string clé in dico.Keys) {
                Console.WriteLine("clé=" + clé + " valeur=" + dico[clé]);
            }
             // dictionary search
            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");
                }
                 // next search
                Console.Out.Write("Nom recherché (rien pour arrêter) : ");
                nomCherché = Console.ReadLine().Trim();
             }//while
        }
 
        // generic method for displaying elements of an enumerable type
        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);
            }
        }
 
    }
}

  • line 8: a table of string which will be used to initialize the dictionary <string,int>
  • line 11: the <string,int> dictionary
  • lines 12-15: its initialization from the string on line 8
  • line 17: number of dictionary entries
  • line 19: dictionary keys
  • line 21: dictionary values
  • line 29: deletes a dictionary entry
  • line 41: search for a key in the dictionary. If it doesn't exist, the TryGetValue will set 0 in value, because value is numeric. This technique can only be used ici because we know that the value 0 is not in the dictionary.

The results are as follows:

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. Text files

5.5.1. The StreamReader class

The class System.IO.StreamReader can read the contents of a text file. It can, in fact, operate on streams that are not files. Here are some of its properties and methods:

Manufacturers

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

Properties

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

Methods

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.

Here's an example:


using System;
using System.IO;
 
namespace Chap3 {
    class Program {
        static void Main(string[] args) {
             // execution directory
            Console.WriteLine("Répertoire d'exécution : "+Environment.CurrentDirectory);
            string ligne = null;
            StreamReader fluxInfos = null;
             // read contents of infos.txt file
            try {
                 // reading 1
                Console.WriteLine("Lecture 1----------------");
                using (fluxInfos = new StreamReader("infos.txt")) {
                    ligne = fluxInfos.ReadLine();
                    while (ligne != null) {
                        Console.WriteLine(ligne);
                        ligne = fluxInfos.ReadLine();
                    }
                }
                 // reading 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);
            }
        }
    }
}
  • line 8: displays the name of the execution directory
  • lines 12, 27: a try / catch to handle a possible exception.
  • line 15: the structure using flux=new StreamReader(...) is a facility to avoid having to explicitly close the stream after it has been used. This is done automatically as soon as you leave the scope of the using.
  • line 15: the file read is called infos.txt. As it is a relative name, it will be searched for in the execution directory displayed by line 8. If it's not there, an exception will be thrown and handled by try / catch.
  • lines 16-20: the file is read in successive lines
  • line 25: the file is read all at once

The file infos.txt is as follows:

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

and placed in the following folder of the C# project :

We're about to discover that bin/Release is the execution folder when the project is executed with Ctrl-F5.

Execution gives the following results:

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

If line 15, we put the file name xx.txt we get the following results:

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. The StreamWriter class

The class System.IO.StreamReader allows you to write to a text file. Like the StreamReader, it is in fact able to exploit streams that are not files. Here are some of its properties and methods:

Manufacturers

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

Properties

public virtual bool AutoFlush
{get;set;}
fixe le mode d'write to the buffer file associated with the stream. Si
égal à False, l'writing in the stream is not immediate: there are
d'first write to a buffer and then to the file when the
mémoire tampon est pleine sinon l'writing to the file is immediate
(pas de tampon intermédiaire). Par défaut c'is the buffered mode which is
utilisé. Le tampon n'is only written to the file when it is full or
bien lorsqu'it is explicitly emptied by a Flush operation or a
lorsqu'we close the StreamWriter flow with a Close operation. The
AutoFlush=False est le plus efficace lorsqu'we work with
fichiers parce qu'limits disk access. This is the default mode
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'one of
partenaires doit être immédiatement lu par l'other. The writing flow
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".

Methods

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.

Consider the following example:


using System;
using System.IO;
 
namespace Chap3 {
    class Program2 {
        static void Main(string[] args) {
             // execution directory
            Console.WriteLine("Répertoire d'exécution : " + Environment.CurrentDirectory);
             string ligne = nu                        ll; // one line of text
             StreamWriter fluxInfos = nu    ll; // the text file
            try {
                 // text file creation
                using (fluxInfos = new StreamWriter("infos2.txt")) {
                    Console.WriteLine("Mode AutoFlush : {0}", fluxInfos.AutoFlush);
                     // read line typed on keyboard
                    Console.Write("ligne (rien pour arrêter) : ");
                    ligne = Console.ReadLine().Trim();
                     // loop as long as the line entered is not empty
                    while (ligne != "") {
                         // write line to text file
                        fluxInfos.WriteLine(ligne);
                         // read new line on keyboard
                        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);
            }
        }
    }
}
  • line 13: once again, we use the syntax using(stream) to avoid having to explicitly close the flow with a Close. This is done automatically when the using.
  • why a try / catch, lines 11 and 27 ? line 13, we could give a file name in the form /rep1/rep2/ .../file with a path /rep1/rep2/... that doesn't exist, making it impossible to create a file. An exception would then be launched. There are other possible exceptions (full disk, insufficient rights, etc.)

The results are as follows:

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

The file infos2.txt has been created in the bin/Release of the project :

 

5.6. Binary files

The classes System.IO.BinaryReader and System.IO.BinaryWriter are used to read and write binary files.

Consider the following application:

// syntaxe pg texte bin logs
// on lit un fichier texte (texte) et on range son contenu dans un fichier binaire (bin
// le fichier texte a des lignes de la forme nom : age qu'on rangera dans une structure string, int
// (logs) est un fichier texte de logs

The text file has the following contents:

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

jacques : 11
sylvain : 12
xx : -1

xx: yy : zz
xx : yy

The program is as follows:


using System;
using System.IO;
 
// syntax pg text bin logs
// read a text file (text) and store its contents in a binary file (bin)
// the text file has lines of the form name: age, which will be stored in a structure string, int
// (logs) is a text log file
 
namespace Chap3 {
    class Program {
        static void Main(string[] arguments) {
             // you need 3 arguments
            if (arguments.Length != 3) {
                Console.WriteLine("syntaxe : pg texte binaire log");
                Environment.Exit(1);
             }//if
 
             // variables
            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;
             // read text file - write binary file
            try {
                 // open text file in read mode
                input = new StreamReader(arguments[0]);
                 // open binary file for writing
                output = new BinaryWriter(new FileStream(arguments[1], FileMode.Create, FileAccess.Write));
                 // open write log file
                logs = new StreamWriter(arguments[2]);
                 // text file processing
                while ((ligne = input.ReadLine()) != null) {
                     // one more line
                    numLigne++;
                     // empty line?
                    if (ligne.Trim() == "") {
                        // on ignore
                        continue;
                    }
                     // one line name: age
                    champs = ligne.Split(séparateurs);
                     // we need 2 fields
                    if (champs.Length != 2) {
                        // on logue l'erreur
                        logs.WriteLine("La ligne n° [{0}] du fichier [{1}] a un nombre de champs incorrect", numLigne, arguments[0]);
                         // next line
                        continue;
                     }//if
                     // 1st field must be non-empty
                    erreur = false;
                    nom = champs[0].Trim();
                    if (nom == "") {
                        // on logue l'erreur
                        logs.WriteLine("La ligne n° [{0}] du fichier [{1}] a un nom vide", numLigne, arguments[0]);
                        erreur = true;
                    }
                     // the second field must be an integer >=0
                    if (!int.TryParse(champs[1],out age) || age<0) {
                        // on logue l'erreur
                        logs.WriteLine("La ligne n° [{0}] du fichier [{1}] a un âge [{2}] incorrect", numLigne, arguments[0], champs[1].Trim());
                        erreur = true;
                     }//if
                     // if no error, write data to binary file
                    if (!erreur) {
                        output.Write(nom);
                        output.Write(age);
                    }
                     // next line
                 }//while
            }catch(Exception e){
                Console.WriteLine("L'erreur suivante s'est produite : {0}", e.Message);
            } finally {
                 // closing files
                if(input!=null) input.Close();
                if(output!=null) output.Close();
                if(logs!=null) logs.Close();
            }
        }
    }
}

Let's focus on operations concerning the BinaryWriter :

  • line 34: the object BinaryWriter is opened by the

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

The constructor argument must be a stream. Ici is a stream built from a file (FileStream) given :

  • (continued)
    • the name
    • the operation to be performed, ici FileMode.Create to create the
    • access type, ici FileAccess.Write for write access to the file
  • lines 70-73: writing operations
            // on écrit les données dans le fichier binaire
            output.Write(nom);
            output.Write(age);

The class BinaryWriter has a range of methods at its disposal Write overloaded to write the various simple data types

  • line 81: flow closing operation
        output.Close();

The three arguments of the method Main are given to the project (via its properties) [1] and the text file to be used is placed in the bin/Release [2] :

With the following file [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

the results are as follows:

  • in [1], the created binary file [personnes1.bin] and the log file [logs.txt]. The latter has the following contents:
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

The contents of binary file [personnes1.bin] will be given to us by the following program. It also accepts three arguments:

// syntaxe pg bin texte logs
// on lit un fichier binaire bin et on range son contenu dans un fichier texte (texte)
// le fichier binaire a une structure string, int
// le fichier texte a des lignes de la forme nom : age
// logs est un fichier texte de logs

We therefore perform the opposite operation. We read a binary file to create a text file. If the text file produced is identical to the original file, we'll know that the text --> binary --> text conversion has been successful. The code is as follows:


using System;
using System.IO;
 
// syntax pg bin text logs
// read a binary bin file and store its contents in a text file (text)
// the binary file has a structure string, int
// the text file has lines of the form name: age
// logs is a text log file
 
namespace Chap3 {
    class Program2 {
        static void Main(string[] arguments) {
             // you need 3 arguments
            if (arguments.Length != 3) {
                Console.WriteLine("syntaxe : pg binaire texte log");
                Environment.Exit(1);
             }//if
 
             // variables
            string nom = null;
            int age = 0;
            int numPersonne = 1;
            BinaryReader input = null;
            StreamWriter output = null;
            StreamWriter logs = null;
            bool fini;
             // read binary file - write text file
            try {
                 // open binary file for reading
                input = new BinaryReader(new FileStream(arguments[0], FileMode.Open, FileAccess.Read));
                 // open text file for writing
                output = new StreamWriter(arguments[1]);
                 // open write log file
                logs = new StreamWriter(arguments[2]);
                 // binary file processing
                fini = false;
                while (!fini) {
                    try {
                         // read name
                        nom = input.ReadString().Trim();
                         // age reading
                        age = input.ReadInt32();
                         // writing to text file
                        output.WriteLine(nom + ":" + age);
                         // next person
                        numPersonne++;
                    } catch (EndOfStreamException) {
                        fini = true;
                    } catch (Exception e) {
                        logs.WriteLine("L'erreur suivante s'est produite à la lecture de la personne n° {0} : {1}", numPersonne, e.Message);
                    }
                 }//while
            } catch (Exception e) {
                Console.WriteLine("L'erreur suivante s'est produite : {0}", e.Message);
            } finally {
                 // closing files
                if (input != null)
                    input.Close();
                if (output != null)
                    output.Close();
                if (logs != null)
                    logs.Close();
            }
        }
    }
}

Let's focus on operations concerning the BinaryReader :

  • line 30: the object BinaryReader is opened by the

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

The constructor argument must be a stream. Ici is a stream built from a file (FileStream) given :

  • (continued)
    • the name
    • the operation to be performed, ici FileMode.Open to open an existing file
    • access type, ici FileAccess.Read for read access to the file
  • lines 40, 42: reading operations
nom=input.ReadString().Trim();
age=input.ReadInt32();

The class BinaryReader has a range of methods at its disposal ReadXX to read different types of simple data

  • line 60: flow closing operation
        input.Close();

If we run the two programs in a chain transforming personnes1.txt at personnes1.bin then personnes1.bin at personnes2.txt2 we obtain the following results:

  • in [1], the project is configured to run the 2nd application
  • in [2], the arguments passed to Main
  • in [3], the files produced by application execution.

The contents of [personnes2.txt] are as follows:

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

5.7. Regular expressions

The class System.Text.RegularExpressions.Regex allows the use of regular expressions. These allow you to test the format of a character string. For example, we can check that a string representing a date is in dd/mm/yy format. To do this, we use a template and compare the string with this template. In this example, d m and a must be numbers. The model for a valid date format is then "\d\d/\d\d/\d\d" where the symbol \d designates a number. The following symbols can be used in a model:

Caractère
Description
\ 
Marks the next character as special or literal. For example, "n" corresponds to the character "n". "\n" corresponds to a newline character. The sequence "\" corresponds to "\", while "\(" corresponds to "(".
^ 
Corresponds to the start of input.
$ 
Corresponds to the end of input.
* 
Corresponds to the preceding character zero or more times. For example, "zo*" corresponds to "z" or "zoo".
+ 
Corresponds to the preceding character one or more times. For example, "zo+" matches "zoo", but not "z".
? 
Corresponds to the preceding character zero or once. For example, "a?ve?" corresponds to "ve" in "lever".
.
Corresponds to any single character, except the new line character.
(modèle) 
Search the model and memorizes the correspondence. The corresponding substring can be extracted from the Matches using Item [0]...[n]. To find matches with characters in brackets ( ), use "\(" or "\)".
x|y
Corresponds to either x or to y. For example, "z|foot" corresponds to "z" or "foot". "(z|f)oo" corresponds to "zoo" or "foo".
{n}
n is a non-negative integer. Corresponds exactly to n times the character. For example, "o{2}" doesn't correspond to "o" in "Bob," but to the first two "o "s in "fooooot".
{n,} 
n is a non-negative integer. Corresponds to at least n times the character. For example, "o{2,}" does not correspond to "o" in "Bob", but to all the "o "s in "fooooot". "o{1,}" equals "o+" and "o{0,}" equals "o*".
{n,m} 
m and n are non-negative integers. Corresponds to at least n and at most m times the character. For example, "o{1,3}" corresponds to the first three "o "s in "foooooot" and "o{0,1}" is equivalent to "o?".
[xyz] 
Character set. Corresponds to one of the indicated characters. For example, "[abc]" corresponds to "a" in "plat".
[^xyz] 
Negative character set. Corresponds to any character not specified. For example, "[^abc]" corresponds to "p" in "plat".
[a-z] 
Character range. Corresponds to any character in the specified range. For example, "[a-z]" corresponds to any lower-case alphabetic character between "a" and "z".
[^m-z] 
Negative character range. Corresponds to any character not in the specified range. For example, "[^m-z]" corresponds to any character not between "m" and "z".
\b 
Corresponds to a boundary representing a word, in other words, the position between a word and a space. For example, "er\b" corresponds to "er" in "lever", but not to "er" in "verb".
\B 
Corresponds to a boundary that does not represent a word. "en*t\B" corresponds to "ent" in "bien entendu".
\d 
Corresponds to a character representing a digit. Equivalent to [0-9].
\D 
Corresponds to a character not representing a digit. Equivalent to [^0-9].
\f 
Corresponds to a page break character.
\n 
Corresponds to a new line character.
\r 
Corresponds to a carriage return character.
\s 
Corresponds to any white space, including space, tab, page break, etc. Equivalent to "[ \f\r\t\v]".
\S 
Corresponds to any non-white space character. Equivalent to "[^ \f\n\r\t\v]".
\t 
Corresponds to a tab character.
\v 
Corresponds to a vertical tab character.
\w 
Corresponds to any character representing a word and including an underscore. Equivalent to "[A-Za-z0-9_]".
\W 
Corresponds to any character not representing a word. Equivalent to "[^A-Za-z0-9_]".
\num 
Corresponds to num, where num is a positive integer. Refers to stored matches. For example, "(.)\1" corresponds to two consecutive identical characters.
\n
Corresponds to n, where n is an octal escapement value. Octal escape values must contain 1, 2 or 3 digits. For example, "\11" and "\011" both correspond to a tab character. "\0011" is equivalent to "\001" & "1". Octal escape values must not exceed 256. If this were the case, only the first two digits would be taken into account in the expression. Allows ASCII codes to be used in regular expressions.
\xn
Corresponds to n, where n is a hexadecimal escape value. Hexadecimal escape values must contain two digits. For example, "\x41" corresponds to "A". "\x041" is equivalent to "\x04" & "1". Allows ASCII codes to be used in regular expressions.

An element in a model can be present in 1 or more copies. Let's look at a few examples involving the symbol \d which represents 1 digit :

model
meaning
\d
a number
\d?
0 or 1 digit
\d*
0 or more digits
\d+
1 or more digits
\d{2}
2 figures
\d{3,}
at least 3 digits
\d{5,7}
between 5 and 7 digits

Now let's imagine a model capable of describing the expected format for a string:

search string
model
a date in dd/mm/yy format
\d{2}/\d{2}/\d{2}
a time in hh:mm:ss format
\d{2}:\d{2}:\d{2}
an unsigned integer
\d+
a sequence of possibly empty spaces
\s*
an unsigned integer that can be preceded or followed by spaces
\s*\d+\s*
an integer that can be signed and preceded or followed by spaces
\s*[+|-]?\s*\d+\s*
an unsigned real number that can be preceded or followed by spaces
\s*\d+(.\d*)?\s*
a real number that can be signed and preceded or followed by spaces
\s*[+|]?\s*\d+(.\d*)?\s*
a string containing the word just
\bjust\b
  

You can specify where in the chain to search for the model:

model
meaning
^model
the model starts the chain
model$
the model finishes the chain
^model$
the model starts and ends the chain
model
the model is searched everywhere in the chain, starting at the beginning.
search string
model
a string ending with an exclamation mark
!$
a string ending with a dot
\.$
a string beginning with the sequence //
^//
a string containing only one word, possibly followed or preceded by spaces
^\s*\w+\s*$
a string containing only two words, possibly followed or preceded by spaces
^\s*\w+\s*\w+\s*$
a string containing the word secret
\bsecret\b

The subsets of a model can be "retrieved". In this way, not only can we check that a string corresponds to a particular model, but we can also retrieve from this string the elements corresponding to the model subsets that have been enclosed in brackets. So if we're analyzing a string containing a date dd/mm/yy and we also want to retrieve the elements dd, mm, yy of this date, we'll use the model (dd)/(dd)/(dd).

5.7.1. Check that a chain corresponds to a given model

An object of type Regex is constructed as follows:

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

Once the template regular expression has been built, it can be compared with strings using the IsMatch :

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

Here's an example:


using System;
using System.Text.RegularExpressions;
 
namespace Chap3 {
    class Program {
        static void Main(string[] args) {
             // a regular expression template
            string modèle1 = @"^\s*\d+\s*$";
            Regex regex1 = new Regex(modèle1);
             // compare a copy with the model
            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
        }
 
    }
}

and performance results :

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

5.7.2. Find all occurrences of a pattern in a chain

The method Matches retrieves the elements of a string corresponding to a :

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

The class MatchCollection has a property Count which is the number of elements in the collection. If results is an object MatchCollection, results[i] is element i of this collection and is of type Match. The class Match has various properties, including the following:

  • Value : object value Match, an element corresponding to the
  • Index : the position where the element was found in the explored chain

Consider the following example:


using System;
using System.Text.RegularExpressions;
 
namespace Chap3 {
    class Program2 {
        static void Main(string[] args) {
             // several occurrences of the model in the copy
            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
        }
    }
}
  • line 8: the pattern sought is a sequence of numbers
  • line 10: the string in which to search for this model
  • line 11: all elements of the copy3 verifying the model model2
  • lines 14-16: they are displayed

The results of the program are as follows:

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. Retrieve parts of a model

Subsets of a model can be "retrieved". In this way, not only can we check that a chain corresponds to a particular model, but we can also retrieve from this chain the elements corresponding to the subsets of the model that have been enclosed in brackets. So if we're analyzing a string containing a date dd/mm/yy and we also want to retrieve the elements dd, mm, yy of this date, we'll use the model (dd)/(dd)/(dd).

Consider the following example:


using System;
using System.Text.RegularExpressions;
 
namespace Chap3 {
    class Program3 {
        static void Main(string[] args) {
             // capture elements in the model
            string modèle3 = @"(\d\d):(\d\d):(\d\d)";
            Regex regex3 = new Regex(modèle3);
            string exemplaire4 = "Il est 18:05:49";
             // model checking
            Match résultat = regex3.Match(exemplaire4);
            if (résultat.Success) {
                 // the copy corresponds to the model
                Console.WriteLine("L'exemplaire [{0}] correspond au modèle [{1}]",exemplaire4,modèle3);
                 // display groups of parentheses
                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 {
                 // the copy does not correspond to the model
                Console.WriteLine("L'exemplaire[{0}] ne correspond pas au modèle [{1}]", exemplaire4, modèle3);
            }
        }
    }
}

Running this program produces the following results:

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

The novelty is in lines 12-19:

  • line 12: the chain exemplary4 is compared with the regex3 through the Match. This makes an object Match already presented. We use ici two new properties of this class:
  • Success (line 13): indicates whether there was a match
  • Groups (lines 17, 18): collection where
    • Groups[0] is the part of the string corresponding to the model
    • Groups[i] (i>=1) corresponds to parenthesis group no. i

If result type is Match, results.Groups type is GroupCollection and results.Groups[i] type Group. The class Group has two properties that we use ici :

  • Value (line 18): object value Group which is the element corresponding to the contents of a parenthesis
  • Index (line 18): the position where the element was found in the explored chain

5.7.4. A learning program

Finding the regular expression to check that a string matches a certain pattern can be a real challenge. The following program gives you a chance to practice. It asks for a pattern and a string, and indicates whether or not the string matches the pattern.


using System;
using System.Text.RegularExpressions;
 
namespace Chap3 {
    class Program4 {
        static void Main(string[] args) {
             // data
            string modèle, chaine;
            Regex regex = null;
            MatchCollection résultats;
             // the user is asked for models and samples to compare with this one
            while (true) {
                 // the model is requested
                Console.Write("Tapez le modèle à tester ou rien pour arrêter :");
                modèle = Console.In.ReadLine();
                 // finished?
                if (modèle.Trim() == "")
                    break;
                 // we create the regular expression
                try {
                    regex = new Regex(modèle);
                } catch (Exception ex) {
                    Console.WriteLine("Erreur : " + ex.Message);
                    continue;
                }
                 // the user is asked for the specimens to be compared with the model
                while (true) {
                    Console.Write("Tapez la chaîne à comparer au modèle [{0}] ou rien pour arrêter :", modèle);
                    chaine = Console.ReadLine();
                     // finished?
                    if (chaine.Trim() == "")
                        break;
                     // we make the comparison
                    résultats = regex.Matches(chaine);
                     // success?
                    if (résultats.Count == 0) {
                        Console.WriteLine("Je n'ai pas trouvé de correspondances");
                        continue;
                     }//if
                     // the elements corresponding to the model are displayed
                    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);
                         // sub-elements
                        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);
                            }
                        }
                    }
                }
            }
        }
    }
}

Here's an example:

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. The Split method

We have already encountered this method in the 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

The method Split class Regex allows us to express the separator in terms of a :


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.

For example, suppose we have lines in a text file of the form field1, field2, .., fieldn. Fields are separated by commas, which can be preceded or followed by spaces. The Split class string is not appropriate. The RegEx provides the solution. If line is the line read, the fields can be obtained by

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

as shown in the following example:


using System;
using System.Text.RegularExpressions;
 
namespace Chap3 {
    class Program5 {
        static void Main(string[] args) {
             // a line
            string ligne = "abc  , def  , ghi";
             // a model
            Regex modèle = new Regex(@"\s*,\s*");
             // decomposition of line into fields
            string[] champs = modèle.Split(ligne);
             // display
            for (int i = 0; i < champs.Length; i++) {
                Console.WriteLine("champs[{0}]=[{1}]", i, champs[i]);
            }
        }
    }
}

Performance results :

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

5.8. Sample application - V3

We return to the application studied in paragraphs 3.6 (version 1) and 4.10 (version 2).

In the latest version studied, the tax calculation was performed in the abstract class AbstractImpot :


namespace Chap2 {
    abstract class AbstractImpot : IImpot {
 
         // tax brackets required to calculate tax
         // come from an external source
 
        protected TrancheImpot[] tranchesImpot;
 
         // tAX CALCULATION
        public int calculer(bool marié, int nbEnfants, int salaire) {
             // calculating the number of shares
            decimal nbParts;
            if (marié) nbParts = (decimal)nbEnfants / 2 + 2;
            else nbParts = (decimal)nbEnfants / 2 + 1;
            if (nbEnfants >= 3) nbParts += 0.5M;
             // calculation of taxable income & family quota
            decimal revenu = 0.72M * salaire;
            decimal QF = revenu / nbParts;
             // tAX CALCULATION
            tranchesImpot[tranchesImpot.Length - 1].Limite = QF + 1;
            int i = 0;
            while (QF > tranchesImpot[i].Limite) i++;
             // return result
            return (int)(revenu * tranchesImpot[i].CoeffR - nbParts * tranchesImpot[i].CoeffN);
         }//calculate
     }//class
 
}

The method calculate in line 38 uses the tranchesImpot from line 35, an array not initialized by the AbstractImpot. This is why it is abstract and must be derived to be useful. This initialization was performed by the derived class HardwiredImpot :


using System;
 
namespace Chap2 {
    class HardwiredImpot : AbstractImpot {
 
         // data tables required to calculate the 
        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() {
                 // creation of a table of 
            tranchesImpot = new TrancheImpot[limites.Length];
                 // filling
            for (int i = 0; i < tranchesImpot.Length; i++) {
                tranchesImpot[i] = new TrancheImpot { Limite = limites[i], CoeffR = coeffR[i], CoeffN = coeffN[i] };
                }
        }
     }// class
}// namespace

Above, the data required to calculate the tax was hard-coded in the class code. The new version of the example places them in a text file:

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

As the operation of this file may produce exceptions, we create a special class to handle them:


using System;
 
namespace Chap3 {
    class FileImpotException : Exception {
         // error codes
        [Flags]
        public enum CodeErreurs { Acces = 1, Ligne = 2, Champ1 = 4, Champ2 = 8, Champ3 = 16 };
 
         // error code
        public CodeErreurs Code { get; set; }
 
         // manufacturers
        public FileImpotException() {
        }
        public FileImpotException(string message)
            : base(message) {
        }
        public FileImpotException(string message, Exception e)
            : base(message,e) {
        }
    }
}
  • line 4: the class FileImpotException class derivation Exception. It will be used to memorize any errors that may occur during processing of the data text file.
  • line 7: an enumeration representing error codes:
    • Access : error accessing text data file
    • Line : line without the three expected fields
    • Field1 : field no. 1 is incorrect
    • Champ2 : field no. 2 is incorrect
    • Champ3 : field no. 3 is incorrect

Some of these errors can be combined (Field1, Champ2, Champ3). So the enumeration CodeErreurs has been annotated with the [Flags] attribute, which implies that the various enumeration values must be powers of 2. An error on fields 1 and 2 will then result in the error code Field1 | Champ2.

  • line 10: automatic ownership Code will store the error code.
  • lines 15: a constructor for building an object FileImpotException with an error message as parameter.
  • lines 18: a constructor for building an object FileImpotException passing an error message and the exception causing the error as parameters.

The class that initializes the tranchesImpot class AbstractImpot is now as follows:


using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
 
namespace Chap3 {
    class FileImpot : AbstractImpot {
 
        public FileImpot(string fileName) {
             // data
            List<TrancheImpot> listTranchesImpot = new List<TrancheImpot>();
            int numLigne = 1;
             // exception
            FileImpotException fe = null;
             // read the contents of the fileName file, line by line
            Regex pattern = new Regex(@"s*:\s*");
             // initially no error
            FileImpotException.CodeErreurs code = 0;
            try {
                using (StreamReader input = new StreamReader(fileName)) {
                    while (!input.EndOfStream && code == 0) {
                         // current line
                        string ligne = input.ReadLine().Trim();
                         // ignore empty lines
                        if (ligne == "") continue;
                         // line broken down into three fields separated by :
                        string[] champsLigne = pattern.Split(ligne);
                         // do we have 3 fields?
                        if (champsLigne.Length != 3) {
                            code = FileImpotException.CodeErreurs.Ligne;
                        }
                         // 3-field conversions
                        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; ;
                        }
                         // mistake?
                        if (code != 0) {
                            // on note l'erreur
                            fe = new FileImpotException(String.Format("Ligne n° {0} incorrecte", numLigne)) { Code = code };
                        } else {
                             // the new tax bracket is memorized
                            listTranchesImpot.Add(new TrancheImpot() { Limite = limite, CoeffR = coeffR, CoeffN = coeffN });
                             // next line
                            numLigne++;
                        }
                    }
                }
                 // transfer the listImpot list to the tranchesImpot array
                if (code == 0) {
                     // transfer the listImpot list to the tranchesImpot array
                    tranchesImpot = listTranchesImpot.ToArray();
                }
            } catch (Exception e) {
                // on note l'erreur
                fe= new FileImpotException(String.Format("Erreur lors de la lecture du fichier {0}", fileName), e) { Code = FileImpotException.CodeErreurs.Acces };
            }
             // error to report?
            if (fe != null) throw fe;
        }
    }
}
  • line 7: the class FileImpot class derivation AbstractImpot as the HardwiredImpot.
  • line 9: class constructor FileImpot is used to initialize the trancheImpot of its base class AbstractImpot. Its parameter is the name of the text file containing the data.
  • line 11: the field tranchesImpot of the base class AbstractImpot is an array to be filled with data from the filename passed as parameter. Reading a text file is sequential. The number of lines is not known until the entire file has been read. As a result, the tranchesImpot. On will temporarily store the data in the generic list listTranchesImpot.

Remember that the TrancheImpot is a :


namespace Chap3 {
     // a tax bracket
    struct TrancheImpot {
        public decimal Limite { get; set; }
        public decimal CoeffR { get; set; }
        public decimal CoeffN { get; set; }
    }
}
  • line 14 : fe type FileImpotException is used to encapsulate a possible operating error in the text file.
  • line 16: regular expression for the field separator in a line field1:field2:field3 of the text file. Fields are separated by the : character, preceded and followed by any number of spaces.
  • line 18: error code in case of error
  • line 20: text file processing with a StreamReader
  • line 21: loops as long as there is still a line to read and no error has occurred
  • line 27: the line read is divided into fields using the regular expression on line 16
  • lines 29-31: check that the line has three fields - note any errors
  • lines 33-38: convert the three strings into three decimal numbers - note any errors
  • lines 40-43: if an error has occurred, an exception of type FileImpotException is created.
  • lines 44-47: if no error has been detected, the next line of the text file is read, after storing the data from the current line.
  • lines 52-55: at the mouth exit while, generic list data listTranchesImpot are copied into the table tranchesImpot of the base class AbstractImpot. This was the manufacturer's goal.
  • lines 56-59: exception handling. This is encapsulated in an object of type FileImpotException.
  • line 61: if the exception fe of line 18 has been initialized, so it's launched.

The overall C# project is as follows :

  • in [1]: the entire project
  • in [2,3]: properties of file [DataImpot.txt] [2]. The property [Copy to Output Directory] [3] is set to always. This causes the file [DataImpot.txt] to be copied to the folder bin/Release (Release mode) or bin/Debug (Debug mode) on each run. This is where the executable looks for it.
  • in [4]: do the same with file [DataImpotInvalide.txt].

The contents of [DataImpot.txt] are as follows:

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

The contents of [DataImpotInvalide.txt] are as follows:

a:b:c

The test program [Program.cs] has not changed: it's the same as in version 2 paragraph 4.10, with the following difference:


using System;
 
namespace Chap3 {
    class Program {
        static void Main() {
...
             // creation of a IImpot object
            IImpot impot = null;
            try {
                 // creation of a IImpot object
                impot = new FileImpot("DataImpot.txt");
            } catch (FileImpotException e) {
                 // error display
                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);
                 // program stop
                Environment.Exit(1);
            }
 
             // infinite loop
            while (true) {
...
             }//while
        }
    }
}
  • line 8: object tax interface type IImpot
  • line 11: object instantiation tax with an object of type FileImpot. This can generate an exception, which is handled by the try / catch on lines 9 / 12 / 18.

Here are some examples:

With the [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 :

With a [xx] none

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'.]

With the [DataImpotInvalide.txt]

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