Skip to content

5. Classi .NET di uso comune

Qui presentiamo alcune classi di uso frequente della piattaforma .NET. Prima di ciò, vi mostreremo come ottenere informazioni sulle centinaia di classi disponibili. Questa guida è indispensabile anche per lo sviluppatore C# più esperto. La qualità della guida (facile accesso, organizzazione comprensibile, pertinenza delle informazioni, ecc.) può determinare il successo o il fallimento di un ambiente di sviluppo.

5.1. Trova la guida sulle classi .NET

Forniamo qui alcuni suggerimenti su come trovare aiuto con Visual Studio.NET

5.1.1. Aiuto/Indice

  • in [1], selezionare l'opzione dal menu Aiuto/Indice.
  • in [2], selezionare l'opzione Visual C# Express Edition
  • in [3], l'albero della guida di C#
  • in [4], un'altra opzione utile è .NET Framework, che consente di accedere a tutte le classi di .NET Framework.

Diamo un'occhiata ai titoli dei capitoli nella Guida di C#:

  • [1]: una panoramica su C#
  • [2]: una serie di esempi su alcuni aspetti di C#
  • [3]: un corso su C# - potrebbe sostituire il presente documento..
  • [4]: per saperne di più su C#
  • [5]: utile per gli sviluppatori C++ o Java. Aiuta a evitare alcune insidie.
  • [6]: quando cerchi degli esempi, puoi iniziare da lì.
  • [7]: ciò che occorre sapere per creare interfacce grafiche utente
  • [8]: per ottenere il massimo dall'IDE Visual Studio Express
  • [9]: SQL Server Express 2005 è un SGBD di alta qualità distribuito gratuitamente. Verrà utilizzato in questo corso.

La guida di C# è solo una parte di ciò di cui lo sviluppatore ha bisogno. L'altra parte è la guida alle centinaia di classi del framework .NET che gli faciliteranno il lavoro.

  • [1]: seleziona la guida sul framework .NET
  • [2]: la guida si trova nell'SDK di .NET Framework
  • [3]: la sezione "Libreria di classi .NET Framework" presenta tutte le classi .NET in base allo spazio dei nomi a cui appartengono
  • [4]: lo spazio dei nomi System, che è stato utilizzato più spesso negli esempi dei capitoli precedenti
  • [5]: nello spazio dei nomi System, un esempio, in questo caso la struttura DateTime
  • [6]: guida sulla struttura DateTime

5.1.2. Aiuto/Indice/Ricerca

La guida fornita da MSDN è vastissima e potresti non sapere dove cercare. In tal caso, puoi utilizzare l'indice della guida:

  • in [1], utilizzare l'opzione [Aiuto/Indice] se la finestra di aiuto non è già aperta, altrimenti utilizzare [2] in una finestra di aiuto esistente.
  • in [3], specificare il campo in cui deve essere effettuata la ricerca
  • in [4], specificare ciò che si sta cercando, in questo caso una classe
  • in [5], la risposta

Un altro modo per cercare l'aiuto è utilizzare la ricerca nell'aiuto:

  • in [1], utilizzare l'opzione [Aiuto/Cerca] se la finestra della guida non è già aperta, altrimenti utilizzare [2] in una finestra della guida già aperta.
  • in [3], specificare ciò che si sta cercando
  • in [4], filtra i campi di ricerca
  • in [5], la risposta sotto forma di diversi temi in cui è stato trovato il testo cercato.

5.2. Stringhe di caratteri

5.2.1. La classe System.String

La classe System.String è identica al tipo semplice string. Dispone di numerose proprietà e metodi. Eccone alcuni:


public int Length { get; }
numero di caratteri della stringa

public bool EndsWith(string valore)

restituisce vero se la stringa termina con value

public bool StartsWith(string value)

restituisce vero se la stringa inizia con value
public virtual bool Equals(object obj)

restituisce vero se la stringa è uguale a obj - equivalente a stringa==obj

public int IndexOf(string valore, int indiceIniziale)

restituisce la prima posizione nella stringa della
stringa value - la ricerca inizia dal
carattere n° startIndex

public int IndexOf(char value, int startIndex)

idem, ma per il carattere value

public string Insert(int startIndex, string value)

inserisce la stringa value nella stringa in posizione
startIndex
public static string Join(string separator, string[] value)

metodo di classe - restituisce una stringa,
risultante dalla concatenazione dei valori dell'array
value con il separatore separator

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

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

come indexOf ma restituisce l'ultima posizione invece
della prima

public string Replace(char oldChar, char newChar)

restituisce una stringa copia della stringa corrente in cui il
carattere oldChar è stato sostituito dal carattere
newChar

public string[] Split(char[] separator)

la stringa viene vista come una sequenza di campi separati
dai caratteri presenti nell'array
separator. Il risultato è l'array di questi campi

public string Substring(int startIndex, int length)

sottostringa della stringa corrente che inizia nella
posizione startIndex e lunga length caratteri
public string ToLower()
converte la stringa corrente in minuscolo
public string ToUpper()
converte la stringa corrente in maiuscolo
public string Trim()
elimina gli spazi
all'inizio e alla fine

Un punto importante da tenere presente: quando un metodo restituisce una stringa, tale stringa costituisce una sequenza diversa rispetto a quella a cui il metodo è stato applicato. Ad esempio, S1.Trim() restituisce una stringa S2, e S1 e S2 sono due sequenze diverse.

Una stringa C può essere considerata come un array di caratteri. Quindi

  • C[i] è il carattere i di C
  • C.Length è il numero di caratteri in C

Si consideri il seguente esempio:


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

L'esecuzione produce i seguenti risultati:

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]

Vediamo un nuovo esempio:


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

e risultati delle prestazioni:

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

Il metodo Split della classe String viene utilizzato per inserire gli elementi di una stringa di caratteri in un array. La definizione di Split utilizzata qui è la seguente:


    public string[] Split(char[] separator);
separator
array di caratteri. Questi caratteri rappresentano i caratteri utilizzati per separare i campi della stringa. Quindi, se la stringa è "campo1, campo2, campo3", possiamo utilizzare separator=new char[] {','}. Se il separatore è una serie di spazi, utilizzare separator=null.
risultato
array di stringhe in cui ogni elemento dell'array è un campo stringa.

Il metodo Join è un metodo statico della classe String :


    public static string Join(string separator, string[] value);
array di stringhe
array di stringhe
separator
una stringa di caratteri da utilizzare come separatore di campi
risultato
una stringa di caratteri formata dalla concatenazione degli elementi dell'array valore separati dal separatore della stringa.

5.2.2. La classe System.Text.StringBuilder

In precedenza abbiamo detto che i metodi della classe String applicati a una stringa di caratteri S1 generavano un'altra stringa S2. La classe System.Text.StringBuilder consente di manipolare S1 senza dover creare una stringa S2. Ciò migliora le prestazioni evitando la moltiplicazione di stringhe con una durata molto limitata.

La classe ammette vari costruttori:

StringBuilder()
costruttore predefinito
StringBuilder(String value)

costruzione e inizializzazione con value
StringBuilder(String value, int capacity)

costruzione e inizializzazione con value con una dimensione di
capacità di caratteri.

Un oggetto StringBuilder opera con blocchi di capacità caratteri per memorizzare la stringa sottostante. L'impostazione predefinita della capacità è 16. Il terzo costruttore sopra riportato viene utilizzato per specificare la capacità del blocco. Il numero di caratteri di capacità necessari per memorizzare una stringa S viene regolato automaticamente dallo StringBuilder. Esistono costruttori per impostare il numero massimo di caratteri in un oggetto StringBuilder. Per impostazione predefinita, questa capacità massima è 2.147.483.647.

Ecco un esempio per illustrare il concetto di capacità:


using System.Text;
using System;
namespace Chap3 {
    class Program {
        static void Main(string[] args) {
             // str
            StringBuilder str = new StringBuilder("test");
            Console.WriteLine("taille={0}, capacité={1}", str.Length, str.Capacity);
            for (int i = 0; i < 10; i++) {
                str.Append("test");
                Console.WriteLine("taille={0}, capacité={1}", str.Length, str.Capacity);
            }
             // str2
            StringBuilder str2 = new StringBuilder("test",10);
            Console.WriteLine("taille={0}, capacité={1}", str2.Length, str2.Capacity);
            for (int i = 0; i < 10; i++) {
                str2.Append("test");
                Console.WriteLine("taille={0}, capacité={1}", str2.Length, str2.Capacity);
            }
        }
    }
}
  • riga 7: creazione dell'oggetto StringBuilder con una dimensione del blocco di 16 caratteri
  • riga 8: str.Length è il numero attuale di caratteri nella stringa str. str.Capacity è il numero di caratteri che possono essere memorizzati nella stringa str prima di riallocare un nuovo blocco.
  • riga 10: str.Append(String S) concatena la stringa S di tipo String alla riga str di tipo StringBuilder.
  • riga 14: creazione dell'oggetto StringBuilder con una capacità di blocco di 10 caratteri

Risultato dell'esecuzione:

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

Questi risultati mostrano che la classe segue un proprio algoritmo per l'allocazione di nuovi blocchi quando la sua capacità è insufficiente:

  • righe 4-5: aumento della capacità di 16 caratteri
  • righe 8-9: capacità aumentata da 16 a 32 caratteri.

Ecco alcuni dei metodi della classe:


public StringBuilder Append(string value)

aggiunge la stringa value all'oggetto StringBuilder. Restituisce
l'oggetto StringBuilder. Questo metodo è sovrammalato
 per supportare diversi tipi per value: byte,
int, float, double, decimal, ...

public StringBuilder Insert(int index,
string value)

inserisce value nella posizione index. Questo metodo è
sovraccaricato come il precedente per accettare
diversi tipi per value.

public StringBuilder Remove(int index, int length)

elimina length caratteri a partire dalla posizione
indice.

public StringBuilder Replace(string oldValue,
string newValue)

sostituisce in StringBuilder la stringa oldValue con
la stringa newValue. Esiste una versione sovraccaricata
(char oldChar, char newChar).
public String ToString()

converte l'oggetto StringBuilder in un oggetto di tipo
String.

Ecco un esempio:


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

e i relativi risultati:

XYZTstabCD

5.3. Dipinti

Gli array derivano dall'Array :

La classe Array dispone di vari metodi per ordinare un array, cercare un elemento in un array, ridimensionare un array, ecc. Presentiamo alcune delle proprietà e dei metodi di questa classe. Sono quasi tutti sovraccaricati, c.a.d. ed esistono in diverse varianti. Ogni array li eredita.

Proprietà

public int Length {get;}
numero totale di elementi dell'array, indipendentemente dal numero di dimensioni
public int Rank {get;}
numero totale di dimensioni dell'array

Metodi

public static int BinarySearch<T>(T[] array,
 valore)
Restituisce la posizione di [value] in tableau.
public static int BinarySearch<T>(T[] array,
int index, int length, T value)
idem, ma cerca nell'array a partire dalla
posizione [index] e su [length] elementi
public static void Clear(Array array, int indice,
(int lunghezza)
inserisce [length] elementi nell'array a partire dall'
 n° [index] a 0 se numerici, false se booleani, null se riferimenti
public static void Copy(Array source,
Array destinazione, int lunghezza)
copia [length] elementi da source in destination
public int GetLength(int i)
numero di elementi della dimensione n. i nella tabella
public int GetLowerBound(int i)
indice del primo elemento della dimensione n. i
public int GetUpperBound(int i)
indice dell'ultimo elemento della dimensione n. i
public static int IndexOf<T>(T[] tabella,
T valore)
restituisce la posizione del valore nell'array oppure -1 se
il valore non viene trovato.
public static void Resize<T>(ref T[] array,
int n)
ridimensiona la tabella a n elementi. Gli elementi
già presenti vengono conservati.
public static void Sort<T>(T[] array,
IComparer<T> comparatore)

ordina la matrice secondo un ordine definito dal comparatore.
Questo metodo è stato presentato nel paragrafo 4.8.

Il seguente programma illustra l'uso di alcuni metodi dell'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
        }
    }
}
  • righe 27-62: il metodo Input acquisisce gli elementi di un array digitati sulla tastiera. Poiché non è possibile determinare a priori la dimensione dell'array (non ne conosciamo la dimensione finale), è necessario ridimensionarlo per ogni nuovo elemento (riga 57). Un algoritmo più efficiente sarebbe stato quello di allocare spazio all'array in gruppi di N elementi. Tuttavia, un array non è progettato per essere ridimensionato. Questo caso è gestito meglio con una lista (ArrayList, List<T>).
  • righe 75-113: il metodo Search per cercare nella tabella un elemento digitato sulla tastiera. La modalità di ricerca varia a seconda che la tabella sia ordinata o meno. Per un array non ordinato, viene eseguita una ricerca lineare utilizzando l'IndexOf della riga 106. Per una tabella ordinata, viene eseguita una ricerca dicotomica utilizzando il BinarySearch della riga 103.
  • riga 18: la tabella è ordinata per elementi. Utilizziamo Ici, una variante di Spell che ha un solo parametro: l'array da ordinare. La relazione di ordine utilizzata per confrontare gli elementi dell'array è quindi quella implicita di questi elementi. Nel caso di Ici, gli elementi sono numerici. Viene utilizzato l'ordine naturale dei numeri.

I risultati sullo schermo sono i seguenti:

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. Collezioni generiche

Oltre agli array, esistono varie classi per memorizzare collezioni di elementi. Esistono versioni generiche nel namespace System.Collections.Generic e versioni non generiche in System.Collections. Presentiamo due collezioni generiche di uso frequente: la lista e il dizionario.

L'elenco delle collezioni generiche è il seguente:

Image

5.4.1. La classe generica List<T>

La classe System.Collections.Generic.List<T> consente di implementare collezioni di oggetti di tipo T la cui dimensione varia durante l'esecuzione del programma. Un oggetto di tipo List<T> può essere manipolato quasi come un array. Pertanto, l'elemento i di una lista l è indicato con l[i].

Esiste anche un tipo di lista non generico: ArrayList, in grado di memorizzare riferimenti a qualsiasi oggetto. ArrayList è funzionalmente equivalente a List<Object>. Un oggetto ArrayList ha questo aspetto:

Nell'esempio sopra, gli elementi 0, 1 e i nell'elenco puntano a oggetti di tipi diversi. Un oggetto deve essere creato prima di aggiungere il suo riferimento all'elenco ArrayList. Sebbene un ArrayList memorizzi riferimenti a oggetti, è possibile memorizzare numeri. Ciò avviene tramite un meccanismo chiamato Boxing: il numero viene incapsulato in un oggetto O di tipo Object e il riferimento O viene memorizzato nell'elenco. Questo meccanismo è trasparente per lo sviluppatore. È possibile scrivere:

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

Questo produrrà il seguente risultato:

Nell'esempio sopra, il numero 4 è stato incapsulato in un oggetto O e il riferimento a O è stato memorizzato nella lista. Per recuperarlo, scrivi:


            int i = (int)liste[0];

L'operazione Object -> int è chiamata Unboxing. Se una lista è composta interamente da int, dichiararla come List<int> migliora le prestazioni. Infatti, i numeri di tipo int vengono quindi memorizzati nella lista stessa e non in un oggetto al di fuori della lista. Le operazioni di Boxing / Unboxing non hanno più luogo.

Per un oggetto List<T> o T è una classe, la lista memorizza nuovamente i riferimenti agli oggetti di tipo T :

Ecco alcune delle proprietà e dei metodi delle liste generiche:

Proprietà

public int Count {get;}
numero di elementi dell'elenco
public int Capacità {get;}

numero di elementi che l'elenco può contenere prima di essere ridimensionato. Il
ridimensionamento avviene automaticamente. Questo concetto di capacità della lista
è analoga a quella descritta per la classe StringBuilder al paragrafo 5.2.2.

Metodi

public void Add(T item)
aggiunge item alla lista
public int RicercaBinaria<T>(T elemento)
restituisce la posizione di item nell'elenco se presente
altrimenti un numero <0
public int RicercaBinaria<T>(T elemento,
IComparer<T> comparatore)

idem, ma il secondo parametro consente di confrontare due
elementi della lista. L'interfaccia IComparer<T> è stata
presentata al paragrafo 4.8.
public void Clear()
elimina tutti gli elementi della lista
public bool Contains(T item)
restituisce True se item è presente nell'elenco, False in caso contrario
public void CopyTo(T[] array)
copia gli elementi della lista in array.
public int IndexOf(T item)
restituisce la posizione di item in tableau oppure -1 se
il valore non viene trovato.
public void Insert(T item, int index)
inserisce item nella posizione index della lista
public bool Remove(T item)
rimuove item dalla lista. Restituisce True se l'operazione
ha esito positivo, False in caso contrario.
public void RemoveAt(int index)
rimuove l'elemento n° index dall'elenco
public void Sort(IComparer<T> comparatore)

ordina la lista secondo un ordine definito dal comparatore.
 Questo metodo è stato presentato nel paragrafo 4.8.
public void Sort()
ordina la lista secondo l'ordine definito dal tipo degli
elementi della lista
public T[] ToArray()
converte gli elementi della lista in un array

Torniamo all'esempio precedente con un oggetto di tipo Array e ora trattiamolo con un oggetto di tipo List<T>. Poiché la lista è un oggetto simile all'array, il codice cambia molto poco. Presentiamo solo le modifiche più rilevanti:


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
        }
    }
}
  • righe 46-51: il metodo generico Poster<T> ha due parametri:
  • il primo parametro è un testo da scrivere
  • il secondo parametro è un oggetto che implementa l'interfaccia generica IEnumerable<T>:
1
2
3
4
public interface IEnumerable<T>{
    IEnumerator GetEnumerator();
    IEnumerator<T> GetEnumerator();
}

La struttura foreach(T element in elements)* alla riga 48 è valida per qualsiasi oggetto elements che implementi l'interfaccia IEnumerable*. Le tabelle (Array*) e gli elenchi (List&lt;T&gt;*) implementano l'interfaccia IEnumerable&lt;T&gt;*. Il componente Poster* è ugualmente adatto alla visualizzazione sia di tabelle che di elenchi.

I risultati dell'esecuzione del programma sono gli stessi dell'esempio che utilizza l'Array.

5.4.2. La classe Dictionary<TKey,TValue>

La classe System.Collections.Generic.Dictionary<TKey,TValue> viene utilizzata per implementare un dizionario. Un dizionario può essere considerato come un array a due colonne:

chiave
valore
chiave1
valore1
chiave2
valore2
..
...

In classe, le chiavi del dizionario <TKey, TValue> sono di tipo TKey, i valori sono di tipo TValue. Le chiavi sono univoche, ovvero non possono esserci due chiavi identiche. Un dizionario di questo tipo potrebbe apparire così se i tipi TKey e TValue fossero classi:

Il valore associato alla chiave C di un dizionario D è dato dalla notazione D[C]. Questo valore è leggibile e scrivibile. Pertanto, possiamo scrivere:

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

Se la chiave c non esiste nel dizionario D, l'operazione D[c] genera un'eccezione.

I principali metodi e proprietà di Dictionary<TKey,TValue> sono i seguenti:

Produttori

public Dictionary<TKey,TValue>()
costruttore senza parametri - crea un dizionario vuoto.
Esistono molti altri costruttori.

Proprietà

public int Count {get;}
numero di voci (chiave, valore) nel dizionario
public Dictionary<TKey,TValue>.KeyCollection Keys {get;}
raccolta delle chiavi del dizionario.
public Dictionary<TKey,TValue>.ValueCollection Values {get;}
raccolta dei valori del dizionario.

Metodi

public void Add(TKey chiave, TValue valore)
aggiunge la coppia (key, value) al dizionario
public void Clear()
elimina tutte le coppie dal dizionario
public bool ContainsKey (TKey key)
restituisce True se key è una chiave del dizionario,
False altrimenti
public bool ContainsValue (TValue value)
restituisce True se value è un valore del dizionario,
False altrimenti
public void CopyTo(T[] array)
copia gli elementi della lista in array.
public bool Remove(TKey chiave)
rimuove dal dizionario la coppia chiave key.
Restituisce True se l'operazione ha esito positivo, False in caso contrario.
public bool TryGetValue(TKey chiave,
out TValue value)
restituisce in value il valore associato alla chiave key se
quest'ultima esiste, altrimenti restituisce il valore predefinito ( )
valore predefinito del tipo TValue (0 per i numeri, false
per i booleani, null per i riferimenti agli oggetti)

Si consideri il seguente programma di esempio:


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

  • riga 8: una tabella di stringhe che verrà utilizzata per inizializzare il dizionario <string,int>
  • riga 11: il dizionario <string,int>
  • righe 12-15: la sua inizializzazione dalla stringa alla riga 8
  • riga 17: numero di voci del dizionario
  • riga 19: chiavi del dizionario
  • riga 21: valori del dizionario
  • riga 29: elimina una voce del dizionario
  • riga 41: ricerca di una chiave nel dizionario. Se non esiste, TryGetValue imposterà 0 come valore, poiché il valore è numerico. Questa tecnica può essere utilizzata solo qui perché sappiamo che il valore 0 non è presente nel dizionario.

I risultati sono i seguenti:

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. File di testo

5.5.1. La classe StreamReader

La classe System.IO.StreamReader consente di leggere il contenuto di un file di testo. È infatti in grado di operare su flussi che non sono file. Ecco alcune delle sue proprietà e dei suoi metodi:

Produttori

public StreamReader(string path)
crea un flusso di lettura dal file nel percorso path. Il
contenuto del file può essere codificato in vari modi. Esiste un
costruttore che permette di specificare la codifica utilizzata. Per impostazione predefinita,
viene utilizzata la codifica UTF-8.

Proprietà

public bool EndOfStream {get;}
True se il flusso è stato letto interamente

Metodi

public void Close()
chiude il flusso e libera le risorse allocate per
la sua gestione. Da eseguire obbligatoriamente dopo
utilizzo del flusso.
public override int Peek()
restituisce il carattere successivo del flusso senza consumarlo.
Un ulteriore Peek restituirebbe quindi lo stesso
carattere.
public override int Read()
restituisce il carattere successivo del flusso e avanza di un
carattere nel flusso.
public override int Read(char[] buffer,
int index, int count)
legge count caratteri dal flusso e li inserisce in
buffer a partire dalla posizione index. Restituisce il numero
di caratteri letti - può essere 0.
public override string ReadLine()
restituisce la riga successiva del flusso o null se ci si trovava alla
alla fine del flusso.
public override string ReadToEnd()
restituisce la fine del flusso o "" se ci si trovava alla fine del
flusso.

Ecco un esempio:


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);
            }
        }
    }
}
  • riga 8: visualizza il nome della directory di esecuzione
  • righe 12, 27: un try / catch per gestire una possibile eccezione.
  • riga 15: la struttura using flux=new StreamReader(...) è una funzionalità che evita di dover chiudere esplicitamente lo stream dopo che è stato utilizzato. Ciò avviene automaticamente non appena si esce dall'ambito del using.
  • riga 15: il file letto si chiama infos.txt. Trattandosi di un nome relativo, verrà cercato nella directory di esecuzione visualizzata dalla riga 8. Se non è presente, verrà generata un'eccezione che verrà gestita dal try / catch.
  • righe 16-20: il file viene letto in righe successive
  • riga 25: il file viene letto tutto in una volta

Il file infos.txt è il seguente:

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

e inserito nella seguente cartella del progetto C#:

Stiamo per scoprire che "bin/Release" è la cartella di esecuzione quando il progetto viene eseguito con Ctrl-F5.

L'esecuzione produce i seguenti risultati:

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

Se alla riga 15 inseriamo il nome del file xx.txt, otteniamo i seguenti risultati:

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. La classe StreamWriter

La classe System.IO.StreamReader consente di scrivere su un file di testo. Come StreamReader, è infatti in grado di gestire flussi che non sono file. Ecco alcune delle sue proprietà e dei suoi metodi:

Produttori

public StreamWriter(string path)
crea un flusso di scrittura nel file al percorso path. Il
contenuto del file può essere codificato in vari modi. Esiste un
costruttore che permette di specificare la codifica utilizzata. Per impostazione predefinita,
viene utilizzata la codifica UTF-8.

Proprietà

public virtual bool AutoFlush
{get;set;}
imposta la modalità di scrittura nel file buffer associato allo stream. Se
è uguale a False, la scrittura nel flusso non è immediata:
prima si scrive in un buffer e poi nel file quando il
buffer è pieno; altrimenti la scrittura sul file è immediata
(senza buffer intermedio). Per impostazione predefinita viene utilizzata la modalità con buffer
utilizzata. Il buffer viene scritto sul file solo quando è pieno o
quando viene svuotato esplicitamente da un'operazione Flush o
quando si chiude lo StreamWriter con un'operazione Close. L'
AutoFlush=False è più efficace quando si lavora con
file perché limita l'accesso al disco. Questa è la modalità predefinita
per questo tipo di flusso. La modalità AutoFlush=False non è adatta a tutti i
flussi, in particolare i flussi di rete. Per questi ultimi, che spesso avvengono
in un dialogo tra due partner, ciò che viene scritto da uno dei
deve essere immediatamente letto dall'altro. Il flusso di scrittura
deve quindi essere in modalità AutoFlush=True.
public virtual string NewLine {get;set;}
i caratteri di fine riga. Per impostazione predefinita "\r\n". Per un sistema Unix,
si dovrebbe utilizzare "\n".

Metodi

public void Close()
chiude il flusso e libera le risorse allocate per la sua
gestione. Da eseguire obbligatoriamente dopo l'utilizzo del flusso.
public override void Flush()
scrive nel file il buffer del flusso, senza attendere che
sia pieno.
public virtual void Write(T value)
scrive value nel file associato al flusso. Qui T non è
un tipo generico ma simboleggia il fatto che il metodo
Write accetti diversi tipi di parametri (string, int,
object, ...). Il metodo value.ToString viene utilizzato per
generare la stringa scritta nel file.
public virtual void WriteLine(T value)
stessa cosa di Write ma con il carattere di fine riga
(NewLine) in più.

Si consideri il seguente esempio:


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);
            }
        }
    }
}
  • riga 13: ancora una volta, utilizziamo la sintassi using(stream) per evitare di dover chiudere esplicitamente il flusso con un Close. Ciò avviene automaticamente quando il using.
  • perché un try / catch, righe 11 e 27? riga 13, potremmo specificare un nome di file nella forma /rep1/rep2/ .../file con un percorso /rep1/rep2/... che non esiste, rendendo impossibile la creazione del file. Verrebbe quindi generata un'eccezione. Esistono altre possibili eccezioni (disco pieno, diritti insufficienti, ecc.)

I risultati sono i seguenti:

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

Il file infos2.txt è stato creato nella cartella bin/Release del progetto:

 

5.6. File binari

Le classi System.IO.BinaryReader e System.IO.BinaryWriter vengono utilizzate per leggere e scrivere file binari.

Si consideri la seguente applicazione:

// 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

Il file di testo ha il seguente contenuto:

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

jacques : 11
sylvain : 12
xx : -1

xx: yy : zz
xx : yy

Il programma è il seguente:


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

Concentriamoci sulle operazioni relative al BinaryWriter:

  • riga 34: l'oggetto BinaryWriter viene aperto dal

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

L'argomento del costruttore deve essere uno stream. Qui si tratta di uno stream creato a partire da un file (FileStream) dato:

  • (continua)
    • il nome
    • l'operazione da eseguire, qui FileMode.Create per creare il
    • tipo di accesso, qui FileAccess.Write per l'accesso in scrittura al file
  • righe 70-73: operazioni di scrittura
            // on écrit les données dans le fichier binaire
            output.Write(nom);
            output.Write(age);

La classe BinaryWriter dispone di una serie di metodi Write sovraccaricati per scrivere i vari tipi di dati semplici

  • riga 81: operazione di chiusura del flusso
        output.Close();

I tre argomenti del metodo Main vengono forniti al progetto (tramite le sue proprietà) [1] e il file di testo da utilizzare viene inserito nella cartella bin/Release [2] :

Con il seguente 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

i risultati sono i seguenti:

  • in [1], il file binario creato [personnes1.bin] e il file di log [logs.txt]. Quest'ultimo ha il seguente contenuto:
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

Il contenuto del file binario [personnes1.bin] ci verrà fornito dal seguente programma. Esso accetta anche tre argomenti:

// 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

Eseguiamo quindi l'operazione opposta. Leggiamo un file binario per creare un file di testo. Se il file di testo prodotto è identico al file originale, sapremo che la conversione testo --> binario --> testo ha avuto esito positivo. Il codice è il seguente:


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

Concentriamoci sulle operazioni relative al BinaryReader:

  • riga 30: l'oggetto BinaryReader viene aperto dal

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

L'argomento del costruttore deve essere uno stream. Qui si tratta di uno stream creato a partire da un file (FileStream) dato:

  • (continua)
    • il nome
    • l'operazione da eseguire, qui FileMode.Open per aprire un file esistente
    • il tipo di accesso, qui FileAccess.Read per l'accesso in lettura al file
  • righe 40, 42: operazioni di lettura
nom=input.ReadString().Trim();
age=input.ReadInt32();

La classe BinaryReader dispone di una serie di metodi ReadXX per leggere diversi tipi di dati semplici

  • riga 60: operazione di chiusura del flusso
        input.Close();

Se eseguiamo i due programmi in sequenza, trasformando «personnes1.txt» in «personnes1.bin» e poi «personnes1.bin» in «personnes2.txt2», otteniamo i seguenti risultati:

  • in [1], il progetto è configurato per eseguire la seconda applicazione
  • in [2], gli argomenti passati a Main
  • in [3], i file prodotti dall'esecuzione dell'applicazione.

Il contenuto di [personnes2.txt] è il seguente:

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

5.7. Espressioni regolari

La classe System.Text.RegularExpressions.Regex consente l'uso di espressioni regolari. Queste consentono di verificare il formato di una stringa di caratteri. Ad esempio, possiamo verificare che una stringa che rappresenta una data sia nel formato gg/mm/aa. Per farlo, utilizziamo un modello e confrontiamo la stringa con questo modello. In questo esempio, g, m e a devono essere numeri. Il modello per un formato di data valido è quindi "\d\d/\d\d/\d\d", dove il simbolo \d indica un numero. In un modello è possibile utilizzare i seguenti simboli:

Carattere
Descrizione
\
Contrassegna il carattere successivo come speciale o letterale. Ad esempio, "n" corrisponde al carattere "n". "\n" corrisponde a un carattere di nuova riga. La sequenza "\" corrisponde a "\", mentre "\(" corrisponde a "(".
^
Corrisponde all'inizio dell'input.
$
Corrisponde alla fine dell'input.
*
Corrisponde al carattere precedente zero o più volte. Ad esempio, "zo*" corrisponde a "z" o "zoo".
+
Corrisponde al carattere precedente una o più volte. Ad esempio, "zo+" corrisponde a "zoo", ma non a "z".
?
Corrisponde al carattere precedente zero o una volta. Ad esempio, "a?ve?" corrisponde a "ve" in "lever".
.
Corrisponde a qualsiasi singolo carattere, eccetto il carattere di nuova riga.
(modello)
Cerca nel modello e memorizza la corrispondenza. La sottostringa corrispondente può essere estratta dai Risultati utilizzando Elemento [0]...[n]. Per trovare corrispondenze con caratteri tra parentesi ( ), usa "\(" o "\)".
x|y
Corrisponde a x o a y. Ad esempio, "z|foot" corrisponde a "z" o a "foot". "(z|f)oo" corrisponde a "zoo" o a "foo".
{n}
n è un numero intero non negativo. Corrisponde esattamente a n volte il carattere. Ad esempio, "o{2}" non corrisponde a "o" in "Bob", ma alle prime due "o" in "fooooot".
{n,}
n è un numero intero non negativo. Corrisponde ad almeno n volte il carattere. Ad esempio, "o{2,}" non corrisponde alla "o" in "Bob", ma a tutte le "o" in "fooooot". "o{1,}" equivale a "o+" e "o{0,}" equivale a "o*".
{n,m}
m e n sono numeri interi non negativi. Corrisponde ad almeno n e al massimo m volte il carattere. Ad esempio, "o{1,3}" corrisponde alle prime tre "o" in "foooooot" e "o{0,1}" è equivalente a "o?".
[xyz]
Insieme di caratteri. Corrisponde a uno dei caratteri indicati. Ad esempio, "[abc]" corrisponde alla "a" in "plat".
[^xyz]
Insieme di caratteri negativi. Corrisponde a qualsiasi carattere non specificato. Ad esempio, "[^abc]" corrisponde alla "p" in "plat".
[a-z]
Intervallo di caratteri. Corrisponde a qualsiasi carattere compreso nell'intervallo specificato. Ad esempio, "[a-z]" corrisponde a qualsiasi carattere alfabetico minuscolo compreso tra "a" e "z".
[^m-z]
Intervallo di caratteri negativo. Corrisponde a qualsiasi carattere non compreso nell'intervallo specificato. Ad esempio, "[^m-z]" corrisponde a qualsiasi carattere non compreso tra "m" e "z".
\b
Corrisponde a un confine che rappresenta una parola, in altre parole, la posizione tra una parola e uno spazio. Ad esempio, "er\b" corrisponde a "er" in "lever", ma non a "er" in "verb".
\B
Corrisponde a un confine che non rappresenta una parola. "en*t\B" corrisponde a "ent" in "bien entendu".
\d
Corrisponde a un carattere che rappresenta una cifra. Equivalente a [0-9].
\D
Corrisponde a un carattere che non rappresenta una cifra. Equivalente a [^0-9].
\f
Corrisponde a un carattere di interruzione di pagina.
\n
Corrisponde a un carattere di nuova riga.
\r
Corrisponde a un carattere di ritorno a capo.
\s
Corrisponde a qualsiasi carattere di spaziatura, inclusi spazio, tabulazione, interruzione di pagina, ecc. Equivalente a "[ \f\r\t\v]".
\S
Corrisponde a qualsiasi carattere non spazio bianco. Equivalente a "[^ \f\n\r\t\v]".
\t
Corrisponde a un carattere di tabulazione.
\v
Corrisponde a un carattere di tabulazione verticale.
\w
Corrisponde a qualsiasi carattere che rappresenti una parola, compreso il trattino basso. Equivalente a "[A-Za-z0-9_]".
\W
Corrisponde a qualsiasi carattere che non rappresenti una parola. Equivalente a "[^A-Za-z0-9_]".
\num
Corrisponde a num, dove num è un numero intero positivo. Si riferisce alle corrispondenze memorizzate. Ad esempio, "(.)\1" corrisponde a due caratteri identici consecutivi.
\n
Corrisponde a n, dove n è un valore di escape ottale. I valori di escape ottali devono contenere 1, 2 o 3 cifre. Ad esempio, "\11" e "\011" corrispondono entrambi a un carattere di tabulazione. "\0011" è equivalente a "\001" e "1". I valori di escape ottali non devono superare 256. In tal caso, nell'espressione verrebbero prese in considerazione solo le prime due cifre. Consente l'uso dei codici ASCII nelle espressioni regolari.
\xn
Corrisponde a n, dove n è un valore di escape esadecimale. I valori di escape esadecimali devono contenere due cifre. Ad esempio, "\x41" corrisponde a "A". "\x041" è equivalente a "\x04" e "1". Consente l'uso dei codici ASCII nelle espressioni regolari.

Un elemento in un modello può essere presente in una o più copie. Vediamo alcuni esempi che coinvolgono il simbolo \d, che rappresenta una cifra:

modello
significato
\d
un numero
\d?
0 o 1 cifra
\d*
0 o più cifre
\d+
1 o più cifre
\d{2}
2 cifre
\d{3,}
almeno 3 cifre
\d{5,7}
tra 5 e 7 cifre

Ora immaginiamo un modello in grado di descrivere il formato previsto per una stringa:

stringa di ricerca
modello
una data nel formato gg/mm/aa
\d{2}/\d{2}/\d{2}
un'ora nel formato hh:mm:ss
\d{2}:\d{2}:\d{2}
un numero intero senza segno
\d+
una sequenza di spazi eventualmente vuoti
\s*
un numero intero senza segno che può essere preceduto o seguito da spazi
\s*\d+\s*
un numero intero che può essere con segno e preceduto o seguito da spazi
\s*[+|-]?\s*\d+\s*
un numero reale senza segno che può essere preceduto o seguito da spazi
\s*\d+(.\d*)?\s*
un numero reale che può essere con segno e preceduto o seguito da spazi
\s*[+|]?\s*\d+(.\d*)?\s*
una stringa contenente la parola just
\bjust\b
  

È possibile specificare in quale punto della stringa cercare il modello:

modello
significa
^modello
il modello inizia la catena
modello$
il modello conclude la catena
^modello$
il modello inizia e termina la catena
modello
il modello viene cercato ovunque nella catena, a partire dall'inizio.
stringa di ricerca
modello
una stringa che termina con un punto esclamativo
!$
una stringa che termina con un punto
\.$
una stringa che inizia con la sequenza //
^//
una stringa contenente una sola parola, eventualmente preceduta o seguita da spazi
^\s*\w+\s*$
una stringa contenente solo due parole, eventualmente preceduta o seguita da spazi
^\s*\w+\s*\w+\s*$
una stringa contenente la parola secret
\bsecret\b

I sottoinsiemi di un modello possono essere "recuperati". In questo modo, non solo possiamo verificare che una stringa corrisponda a un particolare modello, ma possiamo anche estrarre da questa stringa gli elementi corrispondenti ai sottoinsiemi del modello racchiusi tra parentesi. Quindi, se stiamo analizzando una stringa contenente una data gg/mm/aa e vogliamo anche estrarre gli elementi gg, mm, aa di questa data, useremo il modello (gg)/(gg)/(gg).

5.7.1. Verifica che una stringa corrisponda a un dato modello

Un oggetto di tipo Regex viene costruito come segue:

public Regex(string pattern)
crea un oggetto "espressione regolare" a partire da un modello passato
come parametro (pattern)

Una volta costruita l'espressione regolare del modello, è possibile confrontarla con le stringhe utilizzando il metodo IsMatch :

public bool IsMatch(string input)
vero se la stringa input corrisponde al modello dell'espressione
regolare

Ecco un esempio:


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
        }
 
    }
}

e risultati delle prestazioni:

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

5.7.2. Trova tutte le occorrenze di un modello in una stringa

Il metodo Matches recupera gli elementi di una stringa corrispondenti a un :

public MatchCollection Matches(string input)
restituisce la collezione degli elementi della stringa input
corrispondenti al modello

La classe MatchCollection ha una proprietà Count che rappresenta il numero di elementi nella collezione. Se results è un oggetto MatchCollection, results[i] è l'elemento i di questa collezione ed è di tipo Match. La classe Match ha varie proprietà, tra cui le seguenti:

  • Value : oggetto valore Match, un elemento corrispondente all'
  • Index: la posizione in cui l'elemento è stato trovato nella catena esplorata

Si consideri il seguente esempio:


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
        }
    }
}
  • riga 8: il modello cercato è una sequenza di numeri
  • riga 10: la stringa in cui cercare questo modello
  • riga 11: tutti gli elementi di copy3 che verificano il modello model2
  • righe 14-16: vengono visualizzati

I risultati del programma sono i seguenti:

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. Recupera parti di un modello

È possibile "recuperare" sottoinsiemi di un modello. In questo modo, non solo possiamo verificare che una stringa corrisponda a un particolare modello, ma possiamo anche estrarre da questa stringa gli elementi corrispondenti ai sottoinsiemi del modello racchiusi tra parentesi. Quindi, se stiamo analizzando una stringa contenente una data gg/mm/aa e vogliamo anche estrarre gli elementi gg, mm, aa di questa data, useremo il modello (gg)/(gg)/(gg).

Si consideri il seguente esempio:


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

L'esecuzione di questo programma produce i seguenti risultati:

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

La novità si trova nelle righe 12-19:

  • riga 12: la stringa exemplary4 viene confrontata con regex3 tramite Match. Questo crea un oggetto Match già presentato. Qui utilizziamo due nuove proprietà di questa classe:
  • Success (riga 13): indica se c'è stata una corrispondenza
  • Groups (righe 17, 18): collezione in cui
    • Groups[0] è la parte della stringa corrispondente al modello
    • Groups[i] (i>=1) corrisponde al gruppo di parentesi n. i

Se il tipo di risultato è Match, il tipo di results.Groups è GroupCollection e il tipo di results.Groups[i] è Group. La classe Group ha due proprietà che usiamo qui:

  • Valore (riga 18): valore dell'oggetto "Group", che è l'elemento corrispondente al contenuto di una parentesi
  • Indice (riga 18): la posizione in cui l'elemento è stato trovato nella catena esplorata

5.7.4. Un programma di apprendimento

Trovare l'espressione regolare per verificare che una stringa corrisponda a un determinato modello può essere una vera sfida. Il programma seguente ti offre la possibilità di esercitarti. Richiede un modello e una stringa e indica se la stringa corrisponde o meno al modello.


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

Ecco un esempio:

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. Il metodo Split

Abbiamo già incontrato questo metodo in String :


public string[] Split(char[] separator)
la stringa è vista come una sequenza di campi separati dai
caratteri presenti nell'array separator. Il risultato è
l'array di questi campi

Il metodo Split della classe Regex ci permette di esprimere il separatore in termini di un :


public string[] Split(string input)
La stringa input viene scomposta in campi, separati
da un separatore corrispondente al modello dell'oggetto Regex
corrente.

Ad esempio, supponiamo di avere delle righe in un file di testo della forma campo1, campo2, ..., campoN. I campi sono separati da virgole, che possono essere precedute o seguite da spazi. La stringa della classe Split non è adatta. La soluzione è fornita da RegEx. Se line è la riga letta, i campi possono essere ottenuti tramite

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

come mostrato nell'esempio seguente:


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

Risultati delle prestazioni:

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

5.8. Applicazione di esempio - V3

Torniamo all'applicazione studiata nei paragrafi 3.6 (versione 1) e 4.10 (versione 2).

Nell'ultima versione esaminata, il calcolo delle imposte veniva eseguito nella classe astratta 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
 
}

Il metodo calculate alla riga 38 utilizza il tranchesImpot della riga 35, un array non inizializzato dall'AbstractImpot. Questo è il motivo per cui è astratto e deve essere derivato per essere utile. Questa inizializzazione è stata eseguita dalla classe derivata 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

In precedenza, i dati necessari per calcolare l'imposta erano hardcoded nel codice della classe. La nuova versione dell'esempio li colloca in un file di testo:

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

Poiché l'esecuzione di questo file potrebbe generare delle eccezioni, creiamo una classe speciale per gestirle:


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) {
        }
    }
}
  • riga 4: la classe FileImportException deriva dalla classe Exception. Verrà utilizzata per registrare eventuali errori che potrebbero verificarsi durante l'elaborazione del file di testo contenente i dati.
  • riga 7: un'enumerazione che rappresenta i codici di errore:
    • Access: errore durante l'accesso al file di dati di testo
    • Line : riga priva dei tre campi previsti
    • Field1 : il campo n. 1 è errato
    • Champ2: il campo n. 2 non è corretto
    • Campo3: il campo n. 3 non è corretto

Alcuni di questi errori possono essere combinati (Field1, Champ2, Champ3). Pertanto, l'enumerazione CodeErreurs è stata annotata con l'attributo [Flags], il che implica che i vari valori dell'enumerazione devono essere potenze di 2. Un errore sui campi 1 e 2 darà quindi come risultato il codice di errore Field1 | Champ2.

  • riga 10: il codice di proprietà automatica memorizzerà il codice di errore.
  • riga 15: un costruttore per la creazione di un oggetto FileImpotException con un messaggio di errore come parametro.
  • riga 18: un costruttore per creare un oggetto FileImportException passando come parametri un messaggio di errore e l'eccezione che ha causato l'errore.

La classe che inizializza la classe tranchesImpot, AbstractImpot, è ora la seguente:


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;
        }
    }
}
  • riga 7: la classe FileImpot deriva dalla classe AbstractImpot come HardwiredImpot.
  • riga 9: il costruttore della classe FileImpot viene utilizzato per inizializzare il campo tranchesImpot della sua classe base AbstractImpot. Il suo parametro è il nome del file di testo contenente i dati.
  • riga 11: il campo tranchesImpot della classe base AbstractImpot è un array da riempire con i dati provenienti dal file passato come parametro. La lettura di un file di testo è sequenziale. Il numero di righe non è noto finché l'intero file non è stato letto. Di conseguenza, tranchesImpot. On memorizzerà temporaneamente i dati nella lista generica listTranchesImpot.

Ricordiamo che TrancheImpot è un :


namespace Chap3 {
     // a tax bracket
    struct TrancheImpot {
        public decimal Limite { get; set; }
        public decimal CoeffR { get; set; }
        public decimal CoeffN { get; set; }
    }
}
  • riga 14 : il tipo fe FileImportException viene utilizzato per incapsulare un possibile errore operativo nel file di testo.
  • riga 16: espressione regolare per il separatore di campo in una riga campo1:campo2:campo3 del file di testo. I campi sono separati dal carattere :, preceduto e seguito da un numero qualsiasi di spazi.
  • riga 18: codice di errore in caso di errore
  • riga 20: elaborazione del file di testo con uno StreamReader
  • riga 21: cicli finché c'è ancora una riga da leggere e non si è verificato alcun errore
  • riga 27: la riga letta viene suddivisa in campi utilizzando l'espressione regolare della riga 16
  • righe 29-31: verifica che la riga abbia tre campi - annota eventuali errori
  • righe 33-38: conversione delle tre stringhe in tre numeri decimali - segnalazione di eventuali errori
  • righe 40-43: se si è verificato un errore, viene generata un'eccezione di tipo FileImportException.
  • righe 44-47: se non è stato rilevato alcun errore, viene letta la riga successiva del file di testo, dopo aver memorizzato i dati della riga corrente.
  • righe 52-55: all'uscita dal ciclo while, i dati della lista generica listTranchesImpot vengono copiati nella tabella tranchesImpot della classe base AbstractImpot. Questo era l'obiettivo del produttore.
  • righe 56-59: gestione delle eccezioni. Questa è incapsulata in un oggetto di tipo FileImpotException.
  • riga 61: se l'eccezione fe della riga 18 è stata inizializzata, viene lanciata.

Il progetto C# complessivo è il seguente:

  • in [1]: l'intero progetto
  • in [2,3]: proprietà del file [DataImpot.txt] [2]. La proprietà [Copia nella directory di output] [3] è impostata su sempre. Questo fa sì che il file [DataImpot.txt] venga copiato nella cartella bin/Release (modalità Release) o bin/Debug (modalità Debug) ad ogni esecuzione. È qui che l'eseguibile lo cerca.
  • in [4]: fare lo stesso con il file [DataImpotInvalide.txt].

Il contenuto di [DataImpot.txt] è il seguente:

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

Il contenuto del file [DataImpotInvalide.txt] è il seguente:

a:b:c

Il programma di test [Program.cs] non è cambiato: è lo stesso della versione 2, paragrafo 4.10, con la seguente differenza:


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
        }
    }
}
  • riga 8: tipo di interfaccia dell'oggetto IImpot
  • riga 11: istanza dell'oggetto tax con un oggetto di tipo FileImpot. Ciò può generare un'eccezione, che viene gestita dal try / catch alle righe 9 / 12 / 18.

Ecco alcuni esempi:

Con il file [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 :

Con un [xx] nessuno

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

Con il file [DataImpotInvalide.txt]

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