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:
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
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:
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 :
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);
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. | |
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);
string array | |
a character string to be used as a field separator | |
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:
| |
|
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:
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:
| |
| |
| |
| |
|
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:
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
Methods
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:
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:

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 :
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
Methods
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> :
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 :
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
Properties
Methods
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:
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
Properties
Methods
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:
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:
If line 15, we put the file name xx.txt we get the following results:
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
Properties
Methods
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:
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:
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
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
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]:
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:
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
The class BinaryReader has a range of methods at its disposal ReadXX to read different types of simple data
- line 60: flow closing operation
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:
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:
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. | |
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 "\)". | |
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 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 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*". | |
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?". | |
Character set. Corresponds to one of the indicated characters. For example, "[abc]" corresponds to "a" in "plat". | |
Negative character set. Corresponds to any character not specified. For example, "[^abc]" corresponds to "p" in "plat". | |
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". | |
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". | |
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". | |
Corresponds to a boundary that does not represent a word. "en*t\B" corresponds to "ent" in "bien entendu". | |
Corresponds to a character representing a digit. Equivalent to [0-9]. | |
Corresponds to a character not representing a digit. Equivalent to [^0-9]. | |
Corresponds to a page break character. | |
Corresponds to a new line character. | |
Corresponds to a carriage return character. | |
Corresponds to any white space, including space, tab, page break, etc. Equivalent to "[ \f\r\t\v]". | |
Corresponds to any non-white space character. Equivalent to "[^ \f\n\r\t\v]". | |
Corresponds to a tab character. | |
Corresponds to a vertical tab character. | |
Corresponds to any character representing a word and including an underscore. Equivalent to "[A-Za-z0-9_]". | |
Corresponds to any character not representing a word. Equivalent to "[^A-Za-z0-9_]". | |
Corresponds to num, where num is a positive integer. Refers to stored matches. For example, "(.)\1" corresponds to two consecutive identical characters. | |
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. | |
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:
Once the template regular expression has been built, it can be compared with strings using the IsMatch :
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 :
5.7.2. Find all occurrences of a pattern in a chain
The method Matches retrieves the elements of a string corresponding to a :
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:
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:
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:
5.7.5. The Split method
We have already encountered this method in the String :
|
The method Split class Regex allows us to express the separator in terms of a :
|
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
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 :
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:
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]
With a [xx] none
With the [DataImpotInvalide.txt]






























