Skip to content

3. Nozioni di base sul linguaggio C#

3.1. Introduzione

Inizieremo considerando C# come un linguaggio di programmazione classico. Tratteremo le classi in seguito. In un programma ci sono due elementi:

  • i dati
  • le istruzioni che li gestiscono

In genere cerchiamo di separare i dati dalle istruzioni:

3.2. Dati C#

C# utilizza i seguenti tipi di dati:

  1. numeri interi
  2. numeri reali
  3. numeri decimali
  4. caratteri e stringhe
  5. booleani
  6. oggetti

3.2.1. Tipi di dati predefiniti

Tipo C#
Tipo .NET
Dati rappresentati
Suffisso
valori letterali
Codifica
Dominio dei valori
char
Carattere (S)
carattere
 
2 byte
carattere Unicode (UTF-16)
stringa
Stringa (C)
stringa di caratteri
  
riferimento a una sequenza di caratteri Unicode
int
Int32 (S)
numero intero
 
4 byte
[-2³¹; 2³¹-1] [-2147483648; 2147483647]
uint
UInt32 (S)
..
U
4 byte
[0, 2³²-1] [0, 4294967295]
long
Int64 (S)
..
L
8 byte
[-263, 263 -1] [-9223372036854775808, 9223372036854775807]
ulong
UInt64 (S)
..
UL
8 byte
[0, 264 -1] [0, 18446744073709551615]
sbyte
 
..
 
1 byte
[-27, 27 -1] [-128, +127]
byte
Byte (S)
..
 
1 byte
[0, 28 -1] [0,255]
short
Int16 (S)
..
 
2 byte
[-215, 215-1] [-32768, 32767]
ushort
UInt16 (S)
..
 
2 byte
[0, 216-1] [0,65535]
flottante
Singolo (S)
numero reale
F
4 byte
[1,5 × 10⁻⁴⁵, 3,4 × 10³⁸ in termini assoluti
doppio
Doppio (S)
..
D
8 byte
[-1,7 × 10³⁰⁸, 1,7 × 10³⁰⁸ in termini assoluti
decimale
Decimale (S)
numero decimale
M
16 byte
[1,0 10⁻²⁸, 7,9 10²⁸] in valore assoluto con 28 cifre significative
bool
Booleano (S)
..
 
1 byte
vero, falso
oggetto
Oggetto (C)
riferimento a un oggetto
  
riferimento all'oggetto

Sopra, i tipi C# sono indicati dal loro tipo .NET equivalente, con il commento (S) se il tipo è una struttura e (C) se il tipo è una classe. Si è scoperto che esistono due possibili tipi per un intero a 32 bit: int e Int32. Il tipo int è un tipo C#. Int32 è una struttura appartenente a System. Il suo nome completo è System.Int32. Il tipo int è un alias C# per la struttura .NET System.Int32. Analogamente, la stringa C# è un alias per il tipo .NET System.String. System.String è una classe e non una struttura. I due concetti sono simili, ma con la seguente differenza fondamentale:

  • una variabile di tipo Struttura può essere manipolata tramite il suo valore
  • una variabile di tipo Classe può essere manipolata tramite il suo indirizzo (riferimento nel linguaggio degli oggetti).

Una struttura, come una classe, è un tipo complesso con attributi e metodi. Ad esempio, possiamo scrivere:

string nomDuType=3.GetType().FullName;

Quanto sopra è il letterale 3, che in C# è di tipo int per impostazione predefinita, ovvero System.Int32 in .NET. Questa struttura dispone di un metodo GetType() che restituisce un oggetto che incapsula le caratteristiche del tipo di dati System.Int32. Tra queste vi è la proprietà FullName, che restituisce il nome completo del tipo. Come si può notare, il letterale 3 è più complesso di quanto sembri a prima vista.

Ecco un programma che illustra questi punti:


using System;
 
namespace Chap1 {
    class P00 {
        static void Main(string[] args) {
             // example 1
            int ent = 2;
            float fl = 10.5F;
            double d = -4.6;
            string s = "essai";
            uint ui = 5;
            long l = 1000;
            ulong ul = 1001;
            byte octet = 5;
            short sh = -4;
            ushort ush = 10;
            decimal dec = 10.67M;
            bool b = true;
            Console.WriteLine("Type de ent[{1}] : [{0},{2}]", ent.GetType().FullName, ent,sizeof(int));
            Console.WriteLine("Type de fl[{1}]: [{0},{2}]", fl.GetType().FullName, fl, sizeof(float));
            Console.WriteLine("Type de d[{1}] : [{0},{2}]", d.GetType().FullName, d, sizeof(double));
            Console.WriteLine("Type de s[{1}] : [{0}]", s.GetType().FullName, s);
            Console.WriteLine("Type de ui[{1}] : [{0},{2}]", ui.GetType().FullName, ui, sizeof(uint));
            Console.WriteLine("Type de l[{1}] : [{0},{2}]", l.GetType().FullName, l, sizeof(long));
            Console.WriteLine("Type de ul[{1}] : [{0},{2}]", ul.GetType().FullName, ul, sizeof(ulong));
            Console.WriteLine("Type de b[{1}] : [{0},{2}]", octet.GetType().FullName, octet, sizeof(byte));
            Console.WriteLine("Type de sh[{1}] : [{0},{2}]", sh.GetType().FullName, sh, sizeof(short));
            Console.WriteLine("Type de ush[{1}] : [{0},{2}]", ush.GetType().FullName, ush, sizeof(ushort));
            Console.WriteLine("Type de dec[{1}] : [{0},{2}]", dec.GetType().FullName, dec, sizeof(decimal));
            Console.WriteLine("Type de b[{1}] : [{0},{2}]", b.GetType().FullName, b, sizeof(bool));
        }
    }
}
  • riga 7: dichiarazione di un intero ent
  • riga 19: il tipo di una variabile v può essere ottenuto tramite v.GetType().FullName. La dimensione di una struttura S può essere ottenuta tramite sizeof(S). L'istruzione Console.WriteLine("... {0} ... {1} ...", param0, param1, ...) scrive sullo schermo il testo che è il suo primo parametro, sostituendo ogni notazione con {i} con il valore dell'espressione parami.
  • riga 22: l'operatore stringa non può essere utilizzato, poiché designa una classe e non una struttura sizeof.

Ecco il risultato:

Type de ent[2] : [System.Int32,4]
Type de fl[10,5]: [System.Single,4]
Type de d[-4,6] : [System.Double,8]
Type de s[essai] : [System.String]
Type de ui[5] : [System.UInt32,4]
Type de l[1000] : [System.Int64,8]
Type de ul[1001] : [System.UInt64,8]
Type de b[5] : [System.Byte,1]
Type de sh[-4] : [System.Int16,2]
Type de ush[10] : [System.UInt16,2]
Type de dec[10,67] : [System.Decimal,16]
Type de b[True] : [System.Boolean,1]

La visualizzazione produce tipi .NET, non alias C#.

3.2.2. Notazione dei dati letterali

intero int (32 bit)
145, -7, 0xFF (esadecimale)
intero lungo (64 bit) - suffisso L
100000L
reale doppio
134,789, -45E-18 (-45 10-18)
reale float (suffisso F)
134.789F, -45E-18F (-45 10-18)
reale decimale (suffisso M)
100000M
carattere char
'A', 'b'
stringa string
"today" "c:\cap1\paragrafo3" @"c:\cap1\paragrafo3"
booleano bool
vero, falso
data
new DateTime(1954,10,13) (anno, mese, giorno) per il 13/10/1954

Si notino le due stringhe letterali: "c:\chap1\\paragraph3" e @"c:\chap1\paragraph3". Nelle stringhe letterali, il carattere \ viene interpretato. Pertanto, "\n" rappresenta il carattere di fine riga e non la successione dei due caratteri \ e n. Se volessimo ottenere questa successione, dovremmo scrivere "\\n", dove la sequenza viene interpretata come un singolo carattere \. Si potrebbe anche scrivere @"\n" per ottenere lo stesso risultato. La sintassi @"testo" richiede che il testo venga preso esattamente come scritto. Questo viene talvolta chiamato stringa letterale.

3.2.3. Reportistica dei dati

3.2.3.1. Ruolo delle dichiarazioni

Un programma manipola dati caratterizzati da un nome e da un tipo. Questi dati vengono memorizzati in memoria. Quando il programma viene tradotto, il compilatore assegna a ciascun elemento di dati una posizione di memoria caratterizzata da un indirizzo e da una dimensione. Lo fa con l'aiuto delle dichiarazioni effettuate dal programmatore.

Consentono inoltre al compilatore di rilevare errori di programmazione. Ad esempio, l'

x=x*2;

sarà dichiarato errato se x è una stringa, ad esempio.

3.2.3.2. Dichiarazione delle costanti

La sintassi per dichiarare una costante è la seguente:

    const type nom=valeur;          //définit constante nom=valeur

Ad esempio:

const float myPI=3.141592F;    

Perché dichiarare le costanti?

  1. Il programma sarà più facile da leggere se alla costante viene assegnato un nome significativo:
    const  float taux_tva=0.186F;
  1. Modificare il programma sarà più semplice se la "costante" cambia. Ad esempio, nel caso precedente, se l'aliquota IVA cambia al 33%, l'unica modifica da apportare sarà quella di modificare l'istruzione che ne definisce il valore:
    const float taux_tva=0.33F;

Se nel programma fosse stato utilizzato esplicitamente il valore 0,186, sarebbe stato necessario modificare molte istruzioni.

3.2.3.3. Dichiarazione delle variabili

Una variabile è identificata da un nome e fa riferimento a un tipo di dati. C# distingue tra maiuscole e minuscole. Pertanto, FIN ed end sono diversi.

Le variabili possono essere inizializzate al momento della dichiarazione. La sintassi per dichiarare una o più variabili è:

 Identificateur_de_type variable1[=valeur1],variable2=[valeur2],...;

dove Identificatore_di_tipo è un tipo predefinito o un tipo definito dal programmatore. Facoltativamente, una variabile può essere inizializzata oltre che dichiarata.

È anche possibile evitare di specificare il tipo esatto di una variabile utilizzando la parola chiave var al posto di Identificatore_di_tipo :

 var variable1=valeur1,variable2=valeur2,...;

La parola chiave var non significa che le variabili non abbiano un tipo specifico. Alla variabile variablei viene assegnato il valore del tipo di dati valuei. L'inizializzazione è qui obbligatoria affinché il compilatore possa dedurre il tipo della variabile.

Ecco un esempio:


using System;
 
namespace Chap1 {
    class P00 {
        static void Main(string[] args) {
            int i=2;
            Console.WriteLine("Type de int i=2 : {0},{1}",i.GetType().Name,i.GetType().FullName);
            var j = 3;
            Console.WriteLine("Type de var j=3 : {0},{1}", j.GetType().Name, j.GetType().FullName);
            var aujourdhui = DateTime.Now;
            Console.WriteLine("Type de var aujourdhui : {0},{1}", aujourdhui.GetType().Name, aujourdhui.GetType().FullName);
        }
    }
}
  • riga 6: dati tipizzati esplicitamente
  • riga 7: (data).GetType().Name è il nome abbreviato di (data), (data).GetType().FullName è il nome completo di (data)
  • riga 8: dati tipizzati implicitamente. Poiché 3 è di tipo int, j sarà di tipo int.
  • riga 10: dati tipizzati implicitamente. Poiché il tipo DateTime.Now è DateTime, il tipo today sarà DateTime.

All'esecuzione, otteniamo il seguente risultato:

1
2
3
Type de int i=2 : Int32,System.Int32
Type de var j=3 : Int32,System.Int32
Type de var aujourdhui : DateTime,System.DateTime

Una variabile tipizzata implicitamente dalla parola chiave var non può poi cambiare tipo. Ad esempio, dopo la riga 10 del codice, la riga:


            var aujourdhui = "aujourd'hui";

Vedremo più avanti che è possibile dichiarare un tipo "al volo" in un'espressione. In questo caso, si tratta di un tipo anonimo, a cui l'utente non ha assegnato un nome. Il compilatore assegnerà un nome a questo nuovo tipo. Se un tipo anonimo deve essere assegnato a una variabile, l'unico modo per dichiararlo è utilizzare la parola chiave var.

3.2.4. Conversioni tra numeri e stringhe

numero -> stringa
nombre.ToString()
stringa -> int
int.Parse(string) oppure System.Int32.Parse
stringa -> long
long.Parse(string) o System.Int64.Parse
stringa -> double
double.Parse(string) o System.Double.Parse(string)
stringa -> float
float.Parse(string) o System.Float.Parse(string)

La conversione di una stringa in un numero potrebbe non riuscire se la stringa non rappresenta un numero valido. Un errore fatale chiamato exception. Questo errore può essere gestito dal try/catch seguente:


try{
    appel de la fonction susceptible de générer l'exception
} catch (Exception e){
    traiter l'exception e
}
instruction suivante

Se la funzione non genera un'eccezione, si passa all'istruzione successiva; in caso contrario, si passa al corpo della clausola catch e poi all'istruzione successiva. Torneremo più avanti sulla gestione delle eccezioni. Ecco un programma che illustra alcune tecniche per la conversione tra numeri e stringhe. In questo esempio, la funzione poster scrive il valore del suo parametro sullo schermo. Pertanto, poster(S) scrive il valore di S sullo schermo, dove S è di tipo stringa.


using System;
 
namespace Chap1 {
    class P01 {
        static void Main(string[] args) {
 
             // data
            const int i = 10;
            const long l = 100000;
            const float f = 45.78F;
            double d = -14.98;
 
             // number --> string
            affiche(i.ToString());
            affiche(l.ToString());
            affiche(f.ToString());
            affiche(d.ToString());
 
             //boolean --> string
            const bool b = false;
            affiche(b.ToString());
 
             // string --> int
            int i1;
            i1 = int.Parse("10");
            affiche(i1.ToString());
            try {
                i1 = int.Parse("10.67");
                affiche(i1.ToString());
            } catch (Exception e) {
                affiche("Erreur : " + e.Message);
            }
 
             // chain --> long
            long l1;
            l1 = long.Parse("100");
            affiche(l1.ToString());
            try {
                l1 = long.Parse("10.675");
                affiche(l1.ToString());
            } catch (Exception e) {
                affiche("Erreur : " + e.Message);
            }
 
             // chain --> double
            double d1;
            d1 = double.Parse("100,87");
            affiche(d1.ToString());
            try {
                d1 = double.Parse("abcd");
                affiche(d1.ToString());
            } catch (Exception e) {
                affiche("Erreur : " + e.Message);
            }
 
             // string --> float
            float f1;
            f1 = float.Parse("100,87");
            affiche(f1.ToString());
            try {
                d1 = float.Parse("abcd");
                affiche(f1.ToString());
            } catch (Exception e) {
                affiche("Erreur : " + e.Message);
            }
 
         }// fine hand
 
        public static void affiche(string S) {
            Console.Out.WriteLine("S={0}",S);
        }
     }// end of class
}

Le righe 30-32 gestiscono la possibile eccezione che può verificarsi. e.Message è il messaggio di errore associato all'eccezione e.

I risultati sono i seguenti:

S=10
S=100000
S=45,78
S=-14,98
S=False
S=10
S=Erreur : Input string was not in a correct format.
S=100
S=Erreur : Input string was not in a correct format.
S=100,87
S=Erreur : Input string was not in a correct format.
S=100,87
S=Erreur : Input string was not in a correct format.

Si noti che i numeri reali in forma di stringa devono utilizzare la virgola e non il punto decimale. Pertanto, scriviamo

double d1=10.7; 

ma

double d2=int.Parse("10,7");

3.2.5. Tabelle dati

Un array in C# è un oggetto utilizzato per raggruppare dati dello stesso tipo sotto lo stesso identificatore. La sua dichiarazione è la seguente:

Type[] tableau[]=new Type[n]

n è il numero di elementi che l'array può contenere. La sintassi Table[i] indica il dato numero i, dove i appartiene all'intervallo [0,n-1]. Qualsiasi riferimento al dato Table[i] in cui i non appartiene all'intervallo [0,n-1] genererà un'eccezione. Un array può essere inizializzato oltre che dichiarato:

    int[] entiers=new int[] {0,10,20,30};

oppure semplicemente:

    int[] entiers={0,10,20,30};

Gli array hanno una proprietà chiamata Length che indica il numero di elementi presenti nell'array.

Un array bidimensionale può essere dichiarato come segue:

Type[,] array=new Type[n,m];

dove n è il numero di righe, m il numero di colonne. La sintassi Table[i,j] indica l'elemento j nella riga i della tabella. L'array bidimensionale può anche essere inizializzato contemporaneamente alla sua dichiarazione:

    double[,] réels=new double[,] { {0.5, 1.7}, {8.4, -6}};

oppure semplicemente:

    double[,] réels={ {0.5, 1.7}, {8.4, -6}};

Il numero di elementi in ciascuna dimensione può essere ottenuto utilizzando GetLength(i) dove i=0 rappresenta la dimensione corrispondente al primo indice, i=1 la dimensione corrispondente al secondo indice, ...

Il numero totale di dimensioni si ottiene con la proprietà Rank, il numero totale di elementi con la proprietà Length.

Una tabella di tabelle viene dichiarata come segue:

Type[][] array=new Type[n][];

L'istruzione sopra riportata crea un array di n righe. Ogni elemento table[i] è un riferimento ad un array unidimensionale. Questi riferimenti array[i] non vengono inizializzati nella dichiarazione sopra riportata. Il loro valore è il riferimento null.

L'esempio seguente illustra la creazione di un array di tabelle:


            // un tableau de tableaux
            string[][] noms = new string[3][];
            for (int i = 0; i < noms.Length; i++) {
                noms[i] = new string[i + 1];
            }//for
            // initialisation
            for (int i = 0; i < noms.Length; i++) {
                for (int j = 0; j < noms[i].Length; j++) {
                    noms[i][j] = "nom" + i + j;
                }//for j
            }//for i
  • riga 2: una tabella denominata names di tipo string[][]. Ogni elemento è un puntatore ad array (un riferimento a un oggetto) i cui elementi sono di tipo string[].
  • righe 3-5: i 3 elementi della tabella names vengono inizializzati. Ciascuno ora "punta" a un array di elementi di tipo string[]. names[i][j] è l'array di tipo string[] a cui fa riferimento names[i].
  • riga 9: inizializzazione dell'elemento names[i][j] all'interno di un doppio ciclo. Qui names[i] è un array di i+1 elementi. Poiché names[i] è un array, names[i].Length è il suo numero di elementi.

Ecco un esempio dei tre tipi di tabella che abbiamo appena presentato:


using System;
 
namespace Chap1 {
     // tables
 
    using System;
 
     // test class
    public class P02 {
        public static void Main() {
             // an initialized 1-dimensional array
            int[] entiers = new int[] { 0, 10, 20, 30 };
            for (int i = 0; i < entiers.Length; i++) {
                Console.Out.WriteLine("entiers[{0}]={1}", i, entiers[i]);
            }//for
 
             // an initialized 2-dimensional array
            double[,] réels = new double[,] { { 0.5, 1.7 }, { 8.4, -6 } };
            for (int i = 0; i < réels.GetLength(0); i++) {
                for (int j = 0; j < réels.GetLength(1); j++) {
                    Console.Out.WriteLine("réels[{0},{1}]={2}", i, j, réels[i, j]);
                }//for j
            }//for i
 
             // a table of tables
            string[][] noms = new string[3][];
            for (int i = 0; i < noms.Length; i++) {
                noms[i] = new string[i + 1];
            }//for
             // initialization
            for (int i = 0; i < noms.Length; i++) {
                for (int j = 0; j < noms[i].Length; j++) {
                    noms[i][j] = "nom" + i + j;
                }//for j
            }//for i
             // display
            for (int i = 0; i < noms.Length; i++) {
                for (int j = 0; j < noms[i].Length; j++) {
                    Console.Out.WriteLine("noms[{0}][{1}]={2}", i, j, noms[i][j]);
                }//for j
            }//for i
         }//Main
     }//class
}//namespace

All'esecuzione, otteniamo i seguenti risultati:

entiers[0]=0
entiers[1]=10
entiers[2]=20
entiers[3]=30
réels[0,0]=0,5
réels[0,1]=1,7
réels[1,0]=8,4
réels[1,1]=-6
noms[0][0]=nom00
noms[1][0]=nom10
noms[1][1]=nom11
noms[2][0]=nom20
noms[2][1]=nom21
noms[2][2]=nom22

3.3. Istruzioni di base in C#

Si distingue tra

1 le istruzioni elementari eseguite dal computer.

2 istruzioni per il controllo della sequenza del programma.

Le istruzioni elementari diventano chiare se si considera la struttura di un microcomputer e delle sue periferiche.

  1. lettura delle informazioni dalla tastiera

  2. elaborazione delle informazioni

  3. Scrittura delle informazioni sullo schermo

3.3.1. Scrivere sullo schermo

Esistono diverse istruzioni per la scrittura sullo schermo:

Console.Out.WriteLine(expression)
Console.WriteLine(expression)
Console.Error.WriteLine (expression)

dove espressione è qualsiasi tipo di dati che può essere convertito in una stringa per essere visualizzato sullo schermo. Tutti gli oggetti C# o .NET dispongono di un metodo ToString() che viene utilizzato per eseguire questa conversione.

La classe System.Console consente di accedere alle operazioni di scrittura su schermo (Write, WriteLine). La classe Console ha due proprietà, Out ed Error, che sono di tipo TextWriter:

  • Console.WriteLine() è equivalente a Console.Out.WriteLine() e scrive su Out, solitamente associato allo schermo.
  • Console.Error.WriteLine() scrive sullo stream Error, solitamente associato allo schermo.

I flussi Out ed Error possono essere reindirizzati a file di testo durante l'esecuzione del programma, come vedremo tra poco.

3.3.2. Lettura dei dati digitati

Il flusso di dati proveniente dalla tastiera è rappresentato dall'oggetto Console.In di tipo TextReader. Questo tipo di oggetto può essere utilizzato per leggere una riga di testo utilizzando il metodo ReadLine :

    string ligne=Console.In.ReadLine();

La classe Console offre un metodo ReadLine associato per impostazione predefinita a In. Possiamo quindi scrivere:

    string ligne=Console.ReadLine();

La riga digitata sulla tastiera viene memorizzata nella variabile ligne e può quindi essere utilizzata dal programma. Lo stream In può essere reindirizzato a un file, come Out ed Error.

3.3.3. Esempio di input-output

Ecco un breve programma per illustrare le operazioni di I/O da tastiera/schermo:


using System;
 
namespace Chap1 {
     // test class
    public class P03 {
        public static void Main() {
 
             // write to Out feed
            object obj = new object();
            Console.Out.WriteLine(obj);
 
             // write to the Error stream
            int i = 10;
            Console.Error.WriteLine("i=" + i);
 
             // reading a line entered on the keyboard
            Console.Write("Tapez une ligne : ");
            string ligne = Console.ReadLine();
            Console.WriteLine("ligne={0}", ligne);
         }//fine hand
     }//end of class
}
  • riga 9: obj è un riferimento a un oggetto
  • riga 10: obj viene visualizzato sullo schermo. Per impostazione predefinita, viene chiamato obj.ToString().
  • riga 14: è anche possibile scrivere:

            Console.Error.WriteLine("i={0}",i);

Il primo parametro "i={0}" è il formato di visualizzazione, gli altri parametri sono le espressioni da visualizzare. Gli elementi {n} sono parametri "posizionali". In fase di esecuzione, il parametro {n} viene sostituito dal valore dell'espressione n.

Il risultato è il seguente:

1
2
3
4
System.Object
i=10
Tapez une ligne : je suis là
ligne=je suis là
  • riga 1: il risultato visualizzato dalla riga 10 del codice. Il metodo obj.ToString() ha visualizzato il nome del tipo della variabile obj: System.Object. Il tipo object è un alias C# del tipo .NET System.Object.

3.3.4. Reindirizzamento I/O

In DOS e UNIX esistono tre dispositivi standard denominati:

  1. dispositivo di input standard - di default è la tastiera ed è numerato 0
  2. dispositivo di output standard - di default è lo schermo ed è numerato 1
  3. dispositivo di errore standard - di default è lo schermo ed è numerato 2

In C#, Console.Out scrive sul dispositivo 1, il flusso di scrittura Console.Error scrive sul dispositivo 2 e il flusso di lettura Console.In legge i dati dal dispositivo 0.

Quando si esegue un programma in DOS o Unix, è possibile impostare quali dispositivi (0, 1 e 2) verranno utilizzati per il programma in esecuzione. Si consideri la seguente riga di comando:

pg arg1 arg2 .. argn

Come illustrato nel programma argi pg, i dispositivi di I/O standard possono essere reindirizzati verso dei file:

0<in.txt
il flusso di input standard n. 0 viene reindirizzato al file in.txt. Nel programma, Console.In preleverà quindi i propri dati dal file in.txt.
1>out.txt
reindirizza l'output n. 1 al file out.txt. Ciò significa che nel programma Console.Out scriverà i propri dati nel file out.txt
1>>out.txt
idem, ma i dati scritti vengono aggiunti al contenuto corrente del file out.txt.
2>error.txt
reindirizza l'output n. 2 al file error.txt. Ciò significa che nel programma Console.Error scriverà i propri dati nel file error.txt
2>>error.txt
idem, ma i dati scritti vengono aggiunti al contenuto del file corrente error.txt.
1>out.txt 2>error.txt
I dispositivi 1 e 2 vengono entrambi reindirizzati a

Si noti che per reindirizzare i flussi I/O dal pg ai file, non è necessario modificare il pg. Il sistema operativo determina la natura delle periferiche 0, 1 e 2. Si consideri il seguente programma:


using System;
 
namespace Chap1 {
 
     // redirections
    public class P04 {
        public static void Main(string[] args) {
            // lecture flux In
            string data = Console.In.ReadLine();
             // write Out feed
            Console.Out.WriteLine("écriture dans flux Out : " + data);
            // écriture flux Error
            Console.Error.WriteLine("écriture dans flux Error : " + data);
         }//Main
     }//class
}

Generiamo l'eseguibile di questo codice sorgente:

  • in [1]: l'eseguibile viene creato facendo clic con il tasto destro del mouse sul progetto / Build
  • in [2]: in una finestra DOS, l'eseguibile 04.exe è stato creato nella cartella bin/Release del progetto.

Eseguiamo i seguenti comandi nella finestra DOS [2]:

1
2
3
4
5
6
7
8
...\04\bin\Release>echo test >in.txt
...\04\bin\Release>more in.txt
test
...\04\bin\Release>04 0<in.txt 1>out.txt 2>err.txt
...\04\bin\Release>more out.txt
écriture dans flux Out : test
...\04\bin\Release>more err.txt
écriture dans flux Error : test
  • riga 1: la stringa test nel file in.txt
  • righe 2-3: visualizza il contenuto del file in.txt per la verifica
  • riga 4: esecuzione del programma 04.exe. Il flusso In viene reindirizzato a in.txt, il flusso Out a out.txt, il flusso Error a err.txt. L'esecuzione non genera alcuna visualizzazione.
  • righe 5-6: contenuto del file out.txt. Questo contenuto ci mostra che:
  • il file in.txt è stato letto
  • la visualizzazione sullo schermo è stata reindirizzata a out.txt
  • righe 7-8: controllo simile per il file err.txt

Possiamo vedere chiaramente che i flussi Out e In non scrivono sugli stessi dispositivi, poiché sono stati reindirizzati separatamente.

3.3.5. Assegnazione del valore di un'espressione a una variabile

Qui ci interessa l'operazione variable=expression;

L'espressione può essere di tipo: aritmetico, relazionale, booleano, caratteri

3.3.5.1. Interpretazione dell'operazione di assegnazione

L'operazione variabile=espressione;

è essa stessa un'espressione la cui valutazione avviene come segue:

  • viene valutato il lato destro dell'assegnazione: il risultato è un valore V.
  • il valore V viene assegnato alla variabile
  • il valore V è anche il valore dell'assegnazione, questa volta considerata come un'espressione.

Ecco come funziona il

    V1=V2=expression

è valida. A causa della precedenza, verrà valutato l'operatore = più a destra. Abbiamo quindi

    V1=(V2=expression)

L'espressione V2=espressione viene valutata e le viene assegnato il valore V. La valutazione di questa espressione ha fatto sì che V venisse assegnato a V2. L'operatore = successivo viene quindi valutato come:

    V1=V

Il valore di questa espressione è ancora V. La sua valutazione fa sì che V venga assegnato a V1.

Quindi l'espressione V1=V2=

è un'espressione la cui valutazione

  • fa sì che il valore di espressione delle variabili V1 e V2
  • porta al valore di expression.

Questo può essere generalizzato a un'espressione come:

V1=V2=....=Vn=expression

3.3.5.2. Espressione aritmetica

Gli operatori per le espressioni aritmetiche sono i seguenti:

  • somma

  • sottrazione

* moltiplicazione

/ divisione: il risultato è il quoziente esatto se almeno uno degli operandi è reale. Se entrambi gli operandi sono interi, il risultato è il quoziente intero. Quindi 5/2 -> 2 e 5,0/2 -> 2,5.

% divisione: il risultato è il resto, indipendentemente dalla natura degli operandi, essendo il quoziente un numero intero. Si tratta quindi dell'operazione modulo.

Esistono varie funzioni matematiche. Eccone alcune:

double Sqrt(double x)
radice quadrata
double Cos(double x)
coseno
doppio Sin(doppio x)
Sinus
doppio Tan(doppio x)
Tangente
double Pow(double x, double y)
x elevato a y (x>0)
double Exp(double x)
Esponenziale
double Log(double x)
logaritmo neperiano
double Abs(double x)
valore assoluto

ecc...

Tutte queste funzioni sono definite in una classe C# chiamata Math. Quando vengono utilizzate, devono essere precedute dal nome della classe in cui sono definite. Ad esempio, scrivi:

double x, y=4;
x=Math.Sqrt(y);

La definizione completa della classe Math è la seguente:

Image

Image

Image

Image

Image

3.3.5.3. Priorità per la valutazione delle espressioni aritmetiche

La priorità degli operatori nella valutazione di un'espressione aritmetica è la seguente (dalla priorità più alta a quella più bassa):

[fonctions], [ ( )],[ *, /, %], [+, -]

Gli operatori all'interno dello stesso blocco [ ] hanno la stessa priorità.

3.3.5.4. Espressioni relazionali

Gli operatori sono i seguenti:

       <, <=, ==, !=, >, >=

priorità degli operatori

>, >=, <, <=
==, !=

Il risultato di un'espressione relazionale è il valore booleano false se l'espressione è falsa, true in caso contrario.

      bool fin;
      int x=...;
      fin=x>4;

Confronto tra due caratteristiche

Consideriamo due caratteri C1 e C2. Possono essere confrontati utilizzando gli operatori

    <, <=, ==, !=, >, >=

Vengono quindi confrontati i loro codici Unicode, che sono numeri. Nell'ordine Unicode, abbiamo le seguenti relazioni:

espace < .. < '0' < '1' < .. < '9' < .. < 'A' < 'B' < .. < 'Z' < .. < 'a' < 'b' < .. <'z'

Confronto tra due stringhe

Vengono confrontate carattere per carattere. La prima disuguaglianza riscontrata tra due caratteri determina una disuguaglianza dello stesso significato tra le stringhe.

Esempi:

Confronta le stringhe "Cat" e "Dog"

Questa ultima disuguaglianza implica che "Gatto" < "Cane".

Oppure si confrontino le catene "Gatto" e "Gattino". Si ha un pareggio fino a quando la catena "Gatto" non è esaurita. In questo caso, la catena esaurita viene dichiarata la "più piccola". Abbiamo quindi la relazione

    "Chat" < "Chaton".

Funzioni per il confronto tra due stringhe

È possibile utilizzare gli operatori relazionali == e != per verificare l'uguaglianza di due stringhe, oppure la classe Equals di System.String. Per le relazioni < <= > >=, utilizzare la classe CompareTo di System.String:


using System;
 
namespace Chap1 {
    class P05 {
        static void Main(string[] args) {
            string chaine1="chat", chaine2="chien";
            int n = chaine1.CompareTo(chaine2);
            bool egal = chaine1.Equals(chaine2);
            Console.WriteLine("i={0}, egal={1}", n, egal);
            Console.WriteLine("chien==chaine1:{0},chien!=chaine2:{1}", "chien"==chaine1,"chien" != chaine2);
        }
    }
}

Riga 7, la variabile i avrà il valore:

    0    se entrambe le stringhe sono uguali

    1    se il canale 1 è maggiore del canale 2

    -1    se la catena n. 1 è minore della catena n. 2

Riga 8: la variabile egal avrà il valore true se entrambe le stringhe sono uguali, false in caso contrario. La riga 10 utilizza gli operatori == e != per verificare se due stringhe sono uguali o meno.

Risultati dell'esecuzione:

i=-1, egal=False
chien==chaine1:False,chien!=chaine2:False

3.3.5.5. Espressioni booleane

Gli operatori che possono essere utilizzati sono AND (&&) OR(||) NOT (!). Il risultato di un'espressione booleana è un valore booleano.

Priorità degli operatori :

  1. !
  2. &&
  3. ||

            double x = 3.5;
            bool valide = x > 2 && x < 4;

Gli operatori relazionali hanno come operatori di priorità && e ||.

3.3.5.6. Elaborazione bit a bit

Operatori

Siano i e j due numeri interi.

i<<n
sposta i di n bit a sinistra. I bit in entrata sono zeri.
i>>n
sposta i di n bit a destra. Se i è un numero intero con segno (signed char, int, long), il bit di segno viene mantenuto.
i & j
esegue l'OR logico di i e j bit per bit.
i | j
esegue l'operazione logica OR (OU) di i e j bit per bit.
~i
completa i a 1
i^j
effettua l'OR ESCLUSIVO di i e j

Oppure il seguente codice:


short i = 100, j = -13;
ushort k = 0xF123;
Console.WriteLine("i=0x{0:x4}, j=0x{1:x4}, k=0x{2:x4}", i,j,k);
Console.WriteLine("i<<4=0x{0:x4}, i>>4=0x{1:x4},k>>4=0x{2:x4},i&j=0x{3:x4},i|j=0x{4:x4},~i=0x{5:x4},j<<2=0x{6:x4},j>>2=0x{7:x4}", i << 4, i >> 4, k >> 4, (short)(i & j), (short)(i | j), (short)(~i), (short)(j << 2), (short)(j >> 2));
  • il formato {0:x4} visualizza il parametro n° 0 in formato esadecimale (x) con 4 caratteri (4).

I risultati sono i seguenti:

i=0x0064, j=0xfff3, k=0xf123
i<<4=0x0640, i>>4=0x0006,k>>4=0x0f12,i&j=0x0060,i|j=0xfff7,~i=0xff9b,j<<2=0xffcc,j>>2=0xfffc

3.3.5.7. Combinazione di operatori

a=a+b può essere scritto come a+=b

a=a-b può essere scritto come a-=b

Lo stesso vale per gli operatori /, %, *, <<, >>, &, |, ^. Quindi a=a/2; può essere scritto come a/=2;

3.3.5.8. Operatori di incremento e decremento

La notazione variable++ significa variable=variable+1 o anche variable+=1

Scrivere variable-- significa variable=variable-1 o anche variable-=1.

3.3.5.9. L'operatore ternario?

L'espressione

    expr_cond ? expr1:expr2

viene valutata come segue:

1 viene valutata l'espressione expr_cond. Si tratta di un'espressione condizionale con un vero o falso

2 Se vera, il valore dell'espressione è quello di expr1 ed expr2 non viene valutata.

3 Se falsa, si verifica il contrario: il valore dell'espressione è quello di expr2 ed expr1 non viene valutata.

L'operazione i=(j>4 ? j+1:j-1); verrà assegnata alla variabile i: j+1 se j>4, j-1 altrimenti. È come scrivere if(j>4) i=j+1; else i=j-1; ma è più conciso.

3.3.5.10. Priorità generale degli operatori

() []  funzione
gd
! ~ ++ --
dg
nuovo (tipo) operatori di conversione
dg
*  /  %
gd
+  -
gd
<<  >>
gd
< <=  > >= instanceof
gd
==    !=
gd
&
gd
^
gd
|
gd
&&
gd
||
gd
?   :
dg
= += -= ecc. .
dg

gd indica che, in caso di priorità uguale, si osserva la priorità da sinistra a destra. Ciò significa che quando in un'espressione sono presenti operatori di pari priorità, viene valutato per primo l'operatore più a sinistra nell'espressione. dg indica la priorità da destra a sinistra.

3.3.5.11. Modifiche di tipo

In un'espressione, è possibile modificare temporaneamente la codifica di un valore. Questa operazione è nota come modifica del tipo di dati o conversione di tipo. La sintassi per modificare il tipo di un valore in un'espressione è la seguente:

    (type) valeur

Il valore assume quindi il tipo indicato. Ciò comporta una modifica nella codifica del valore.


using System;
 
namespace Chap1 {
    class P06 {
        static void Main(string[] args) {
            int i = 3, j = 4;
            float f1=i/j;
            float f2=(float)i/j;
            Console.WriteLine("f1={0}, f2={1}",f1,f2);
        }
    }
}
  • riga 7, f1 avrà il valore 0.0. La divisione 3/4 è una divisione intera poiché entrambi gli operandi sono di tipo int.
  • riga 8, (float)i è il valore di i trasformato in float. Ora abbiamo una divisione tra un numero reale di tipo float e un intero di tipo int. Si tratta della divisione tra numeri reali. Anche il valore di j verrà trasformato in un float, quindi si dividono i due numeri reali. f2 avrà quindi il valore 0,75.

Ecco i risultati:

f1=0, f2=0,75

In (float)i :

  • i è un valore codificato esatto a 2 byte
  • (float) i è lo stesso valore codificato come approssimazione reale a 4 byte

Il valore di i. Questa transcodifica avviene solo per la durata di un calcolo, e la variabile i mantiene sempre il suo tipo int.

3.4. Istruzioni di controllo del flusso del programma

3.4.1. Stop

L'Exit definito nell'Ambiente interrompe l'esecuzione del programma.

syntaxe        void Exit(int status)
action        arrête le processus en cours et rend la valeur status au processus père

Exit termina il processo corrente e restituisce il controllo al processo chiamante. Il valore di status può essere utilizzato da quest'ultimo. In DOS, questa variabile di stato viene restituita nella variabile di sistema ERRORLEVEL, il cui valore può essere verificato in un file batch. In Unix, con l'interprete di comandi Shell Bourne, la variabile $? recupera il valore di status.

    Environment.Exit(0);

interromperà l'esecuzione del programma con un valore di stato pari a 0.

3.4.2. Struttura di scelta semplice

 syntaxe :  if (condition) {actions_condition_vraie;} else {actions_condition_fausse;}

note:

  • la condizione è racchiusa tra parentesi.
  • ogni azione è terminata da un punto e virgola.
  • le parentesi graffe non sono seguite da punti e virgola.
  • le parentesi graffe sono necessarie solo se c'è più di un'azione.
  • la clausola else può essere assente.
  • Non esiste una clausola then.

L'equivalente algoritmico di questa struttura è if .. then ... otherwise :

esempio


    if (x>0)  { nx=nx+1;sx=sx+x;} else dx=dx-x;

Le strutture di scelta possono essere annidate:

if(condition1)
if (condition2)
        {......}
      else         //condition2
         {......}
else         //condition1
     {.......}

A volte si presenta il seguente problema:


using System;
 
namespace Chap1 {
    class P07 {
        static void Main(string[] args) {
            int n = 5;
            if (n > 1)
                if (n > 6)
                    Console.Out.WriteLine(">6");
                else Console.Out.WriteLine("<=6");
        }
    }
}

Nell'esempio precedente, a quale if si riferisce l'else della riga 10? La regola è che un else si riferisce sempre all'if più vicino: if(n>6), riga 8, nell'esempio. Consideriamo un altro esempio:


            if (n2 > 1) {
                if (n2 > 6) Console.Out.WriteLine(">6");
       } else Console.Out.WriteLine("<=1");    

Qui volevamo inserire un else nel caso if(n2>1) e nessun else nel caso if(n2>6). A causa dell'osservazione precedente, siamo obbligati a inserire le parentesi graffe nel caso if(n2>1) {...} else ...

3.4.3. Struttura case

La sintassi è la seguente:

switch(expression) {
    case v1:     
            actions1;
            break;
    case v2:     
            actions2;
            break;
         . .. .. .. .. ..
    default:     
            actions_sinon;
            break;
}

note

  • il valore dello switch può essere un numero intero, un carattere o una stringa
  • l'espressione è racchiusa tra parentesi.
  • la clausola default può essere assente.
  • i valori vi sono valori possibili dell'espressione. Se il valore dell'espressione è vi, vengono eseguite le azioni associate alla clausola case vi.
  • L'istruzione break ci porta fuori dalla struttura case.
  • ogni blocco di istruzioni collegato a un valore vi deve terminare con un'istruzione di diramazione (break, goto, return, ...) altrimenti il compilatore segnala un errore.

esempio

Visita algoritmi

                selon la valeur de choix
                    cas 0
                         fin du module
                    cas 1
                         exécuter module M1
                    cas 2
                        exécuter module M2
                    sinon
                         erreur<--vrai
                findescas

In C#

int choix = 2; 
bool erreur = false;
switch (choix) {
   case 0: return;
   case 1: M1(); break;
   case 2: M2(); break;
   default: erreur = true; break;
}
}// end Main

static void M1() {
    Console.WriteLine("M1");
}

static void M2() {
    Console.WriteLine("M2");
}
}

3.4.4. Strutture di ripetizione

3.4.4.1. Numero noto di ripetizioni

Struttura per

La sintassi è la seguente:


    for (i=id;i<=if;i=i+ip){ 
       actions; 
      } 

Note

  • i 3 argomenti di for sono racchiusi tra parentesi e separati da punti e virgola.
  • Ogni for termina con un punto e virgola.
  • La parentesi graffa è necessaria solo se c'è più di un'azione.
  • La parentesi graffa non è seguita da un punto e virgola.

L'equivalente algoritmico è il ciclo for:

pour i variant de id à if avec un pas de ip
    actions
finpour

che può essere tradotto come:

    i  id
    tantque i<=if
        actions
        i i+ip
    fintantque

Struttura foreach

La sintassi è la seguente:


foreach (Type variable in collection)
    instructions; 
}

Note

  • collection è una raccolta di oggetti enumerabili. La raccolta di oggetti enumerabili che già conosciamo è l'array
  • Il tipo è il tipo degli oggetti nella collezione. Per un array, questo sarebbe il tipo degli elementi dell'array
  • variable è una variabile locale al ciclo che assumerà successivamente come proprio valore tutti i valori presenti nella collezione.

Quindi il codice seguente:


            string[] amis = { "paul", "hélène", "jacques", "sylvie" };
            foreach (string nom in amis) {
                Console.WriteLine(nom);
         }

verrebbe visualizzato:

paul
hélène
jacques
sylvie

3.4.4.2. Numero di ripetizioni sconosciuto

In C# esistono molte strutture per questo caso.

Struttura tantque (while)


    while(condition){
          actions;
        } 

Il ciclo viene eseguito finché la condizione è vera. Il ciclo potrebbe non essere mai eseguito.

Note:

  • la condizione è racchiusa tra parentesi.
  • Ogni azione è terminata da un punto e virgola.
  • la parentesi graffa è necessaria solo se c'è più di un'azione.
  • la parentesi graffa non è seguita da un punto e virgola.

La struttura algoritmica corrispondente è tantque :

tantque condition
    actions
fintantque

Ripeti la struttura fino a quando (do while)

La sintassi è la seguente:


    do{
       instructions;
    }while(condition); 

Il ciclo continua finché la condizione non diventa falsa. In questo caso il ciclo viene eseguito almeno una volta.

note

  • la condizione è racchiusa tra parentesi.
  • Ogni azione è terminata da un punto e virgola.
  • la parentesi graffa è necessaria solo se c'è più di un'azione.
  • la parentesi graffa non è seguita da un punto e virgola.

La struttura algoritmica corrispondente è repeat ... until :

répéter
    actions
jusqu'à condition

Struttura per "in generale" (for)

La sintassi è la seguente:


for(instructions_départ;condition;instructions_fin_boucle){
    instructions; 
}

Il ciclo continua finché la condizione è vera (valutata prima di ogni iterazione). Le istruzioni_inizio vengono eseguite prima di entrare nel ciclo per la prima volta. Le istruzioni_fine_ciclo vengono eseguite dopo ogni iterazione.

note

  • le varie istruzioni in instructions_depart e instructions_fin_boucle sono separate da virgole.

La struttura algoritmica corrispondente è la seguente:

instructions_départ
tantque condition
    actions
    instructions_fin_boucle
fintantque

Esempi

I seguenti frammenti di codice calcolano tutti la somma dei primi 10 numeri interi.

            int i, somme, n=10;
            for (i = 1, somme = 0; i <= n; i = i + 1)
                somme = somme + i;

            for (i = 1, somme = 0; i <= n; somme = somme + i, i = i + 1) ;

            i = 1; somme = 0;
            while (i <= n) { somme += i; i++; }

            i = 1; somme = 0;
            do somme += i++;
            while (i <= n);

3.4.4.3. Istruzioni di gestione dei cicli

break
Uscite per loop, while, do ... while.
continue
fa avanzare i cicli for, while, do ... alla successiva iterazione. while

3.5. Gestione delle eccezioni

Molte funzioni C# possono generare eccezioni, ovvero errori. Quando una funzione può generare un'eccezione, il programmatore dovrebbe gestirla con l'obiettivo di ottenere programmi più resistenti agli errori: bisogna sempre evitare il "crash" improvviso di un'applicazione.

Un'eccezione viene gestita come segue:

try{
    code susceptible de générer une exception
} catch (Exception e){
    traiter l'exception e
}
instruction suivante

Se la funzione non genera un'eccezione, si passa all'istruzione successiva; in caso contrario, si passa al corpo della clausola catch e poi all'istruzione successiva. Si notino i seguenti punti:

  • e è un oggetto di tipo Exception o derivato. È possibile essere più precisi utilizzando tipi quali IndexOutOfRangeException, FormatException, SystemException, ecc.: esistono diversi tipi di eccezione. Scrivendo catch (Exception e), si indica che si desidera gestire tutti i tipi di eccezione. Se il codice nel try è suscettibile di generare diversi tipi di eccezione, si potrebbe voler essere più precisi gestendo l'eccezione con diversi catch:
try{
    code susceptible de générer les exceptions
} catch ( IndexOutOfRangeException e1){
    traiter l'exception e1
}
} catch ( FormatException e2){
    traiter l'exception e2
}
instruction suivante
  • È possibile aggiungere alle clausole try/catch una clausola finally:
try{
    code susceptible de générer une exception
} catch (Exception e){
    traiter l'exception e
}
finally{
    code exécuté après try ou catch
}
instruction suivante

Che ci sia o meno un'eccezione, il codice della clausola finally verrà sempre eseguito.

  • Nel catch, potresti non voler utilizzare l'Exception disponibile. Invece di scrivere catch (Exception e){..}, scriviamo catch(Exception){...} o semplicemente catch {...}.
  • La classe Exception ha una proprietà Message che contiene un messaggio che descrive in dettaglio l'errore verificatosi. Quindi, se vogliamo visualizzarlo, scriveremo:
catch (Exception ex){
    Console.WriteLine("L'erreur suivante s'est produite : {0}",ex.Message);
    ...
}//catch
  • La classe Exception dispone di un metodo ToString che restituisce una stringa indicante il tipo di eccezione e il valore del Message. Possiamo quindi scrivere:
catch (Exception ex){
    Console.WriteLine("L'erreur suivante s'est produite : {0}", ex.ToString());
    ...
}//catch

Possiamo anche scrivere:

catch (Exception ex){
    Console.WriteLine("L'erreur suivante s'est produite : {0}",ex);
    ...
}//catch

Il compilatore assegnerà al parametro {0} il valore ex.ToString().

L'esempio seguente mostra un'eccezione generata dall'uso di un elemento di array inesistente:


using System;
 
namespace Chap1 {
    class P08 {
        static void Main(string[] args) {
             // declaring & initializing an array
            int[] tab = { 0, 1, 2, 3 };
            int i;
             // table display with for
            for (i = 0; i < tab.Length; i++)
                Console.WriteLine("tab[{0}]={1}", i, tab[i]);
             // table display with a for each
            foreach (int élmt in tab) {
                Console.WriteLine(élmt);
            }
             // generating an exception
            try {
                tab[100] = 6;
            } catch (Exception e) {
                Console.Error.WriteLine("L'erreur suivante s'est produite : " + e);
                return;
             }//try-catch
            finally {
                Console.WriteLine("finally ...");
            }
        }
    }
}

Nella riga 18 sopra riportata verrà generata un'eccezione poiché l'array tab non contiene l'elemento n. 100. L'esecuzione del programma produce i seguenti risultati:

tab[0]=0
tab[1]=1
tab[2]=2
tab[3]=3
0
1
2
3
L'erreur suivante s'est produite : System.IndexOutOfRangeException: L'index se trouve en dehors des limites du tableau.
   à Chap1.P08.Main(String[] args) dans C:\data\travail\2007-2008\c# 2008\poly\Chap1\08\Program.cs:ligne 7
finally ...
  • riga 9: si è verificata l'eccezione [System.IndexOutOfRangeException]
  • riga 11: è stata eseguita la clausola finally (righe 23-25) del codice, anche se la riga 21 conteneva un'istruzione return per uscire dal metodo. Si noti che il finally viene sempre eseguito.

Ecco un altro esempio di come gestire l'eccezione causata dall'assegnazione di una stringa a una variabile intera quando la stringa non rappresenta un numero intero:


using System;
 
namespace Chap1 {
    class P08 {
        static void Main(string[] args) {
 
             // example 2
             // We ask for the name
            Console.Write("Nom : ");
             // reading response
            string nom = Console.ReadLine();
             // age requested
            int age = 0;
            bool ageOK = false;
            while (!ageOK) {
                 // question
                Console.Write("âge : ");
                 // read-verify answer
                try {
                    age = int.Parse(Console.ReadLine());
                    ageOK = age>=1;
                } catch {
                 }//try-catch
                if (!ageOK) {
                    Console.WriteLine("Age incorrect, recommencez...");
                }
             }//while
             // final display
            Console.WriteLine("Vous vous appelez {0} et vous avez {1} an(s)",nom,age);
        }
    }
}
  • righe 15-27: il ciclo per l'inserimento dell'età di una persona
  • riga 20: la riga digitata sulla tastiera viene trasformata in un numero intero utilizzando il metodo int.Parse. Questo metodo genera un'eccezione se la conversione non è possibile. Questo è il motivo per cui l'operazione è stata inserita in un try / catch.
  • righe 22-23: se viene generata un'eccezione, si passa al catch dove non viene fatto nulla. Pertanto, la variabile booleana ageOK impostata su false alla riga 14 rimarrà false.
  • riga 21: se si raggiunge questa riga, la conversione stringa -> int è andata a buon fine. Tuttavia, si verifica che il numero intero ottenuto sia maggiore o uguale a 1.
  • righe 24-26: viene visualizzato un messaggio di errore se l'età non è corretta.

Alcuni risultati delle prestazioni:

1
2
3
Nom : dupont
âge : 23
Vous vous appelez dupont et vous avez 23 an(s)
1
2
3
4
5
6
7
Nom : durand
âge : x
Age incorrect, recommencez...
âge : -4
Age incorrect, recommencez...
âge : 12
Vous vous appelez durand et vous avez 12 an(s)

3.6. Esempio di applicazione - V1

Proponiamo di scrivere un programma per calcolare l'imposta sul reddito di un contribuente. Il caso semplificato è quello di un contribuente che deve dichiarare solo il proprio stipendio (dati del 2004 relativi al reddito del 2003):

  • il numero di quote di dipendente viene calcolato come nbParts=nbEnfants/2 +1 se non sposato, nbEnfants/2+2 se sposato, dove nbEnfants è il numero di figli.
  • se ha almeno tre figli, ottiene mezza quota in più
  • Calcola il tuo reddito imponibile: R = 0,72 × S, dove S è il suo stipendio annuale
  • calcola il tuo coefficiente familiare QF=R/nbParts
  • calcola la tua imposta I. Considera la seguente tabella:
4262
0
0
8382
0,0683
291,09
14753
0,1914
1322,92
23888
0,2826
2668,39
38868
0,3738
4846,98
47932
0,4262
6883,66
0
0,4809
9505,54

Ogni riga ha 3 campi. Per calcolare l'imposta I, cerca la prima riga in cui QF<=champ1. Ad esempio, se QF=5000 troviamo la riga

    8382        0.0683        291.09

L'imposta I è quindi pari a 0,0683*R - 291,09*nbParts. Se QF è tale che la relazione QF<=champ1 non viene mai verificata, vengono utilizzati i coefficienti dell'ultima riga. Qui:

0                0,4809    9505,54

il che dà l'imposta I=0,4809*R - 9505,54*nbParts.

Il programma C# corrispondente è il seguente:


using System;
 
namespace Chap1 {
    class Impots {
        static void Main(string[] args) {
             // data tables required for tax calculation
            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 };
 
             // marital status is restored
            bool OK = false;
            string reponse = null;
            while (!OK) {
                Console.Write("Etes-vous marié(e) (O/N) ? ");
                reponse = Console.ReadLine().Trim().ToLower();
                if (reponse != "o" && reponse != "n")
                    Console.Error.WriteLine("Réponse incorrecte. Recommencez");
                else OK = true;
             }//while
            bool marie = reponse == "o";
 
             // number of children
            OK = false;
            int nbEnfants = 0;
            while (!OK) {
                Console.Write("Nombre d'enfants : ");
                try {
                    nbEnfants = int.Parse(Console.ReadLine());
                    OK = nbEnfants >= 0;
                } catch {
                 }// try
                if (!OK) {
                    Console.WriteLine("Réponse incorrecte. Recommencez");
                }
             }// while
 
             // salary
            OK = false;
            int salaire = 0;
            while (!OK) {
                Console.Write("Salaire annuel : ");
                try {
                    salaire = int.Parse(Console.ReadLine());
                    OK = salaire >= 0;
                } catch {
                 }// try
                if (!OK) {
                    Console.WriteLine("Réponse incorrecte. Recommencez");
                }
             }// while
 
             // calculating the number of shares
            decimal nbParts;
            if (marie) nbParts = (decimal)nbEnfants / 2 + 2;
            else nbParts = (decimal)nbEnfants / 2 + 1;
            if (nbEnfants >= 3) nbParts += 0.5M;
 
             // taxable income
            decimal revenu = 0.72M * salaire;
 
             // family quotient
            decimal QF = revenu / nbParts;
 
             // search for tax bracket corresponding to QF
            int i;
            int nbTranches = limites.Length;
            limites[nbTranches - 1] = QF;
            i = 0;
            while (QF > limites[i]) i++;
             // tax
            int impots = (int)(coeffR[i] * revenu - coeffN[i] * nbParts);
 
             // the result is displayed
            Console.WriteLine("Impôt à payer : {0} euros", impots);
        }
    }
}
  • righe 7-9: i valori numerici sono seguiti dal suffisso M (Money) per indicare che sono di tipo decimale.
  • riga 16:
    • Console.ReadLine() rende la stringa C1 tipizzata
    • C1.Trim() rimuove gli spazi iniziali e finali C1 - restituisce una stringa C2
    • C2.ToLower() crea la stringa C3, che è la stringa C2 trasformata in minuscolo.
  • riga 21: la variabile booleana marie riceve il valore true o false in base alla relazione answer=="o"
  • riga 29: la stringa digitata sulla tastiera viene trasformata in un int. Se la trasformazione fallisce, viene generata un'eccezione.
  • riga 30: il booleano OK riceve il valore true o false in base alla relazione nbEnfants>=0
  • righe 55-56: non è possibile scrivere semplicemente nbEnfants/2. Se nbEnfants fosse uguale a 3, avremmo 3/2, una divisione intera che darebbe 1 e non 1,5. Quindi scriviamo (decimal)nbEnfants per rendere uno degli operandi della divisione reale e avere così una divisione tra reali.

Ecco alcuni esempi:

Etes-vous marié(e) (O/N) ? o
Nombre d'enfants : 2
Salaire annuel : 60000
Impôt à payer : 4282 euros
Etes-vous marié(e) (O/N) ? oui
Réponse incorrecte. Recommencez
Etes-vous marié(e) (O/N) ? o
Nombre d'enfants : trois
Réponse incorrecte. Recommencez
Nombre d'enfants : 3
Salaire annuel : 60000 euros
Réponse incorrecte. Recommencez
Salaire annuel : 60000
Impôt à payer : 2959 euros

3.7. Argomenti principali del programma

La funzione principale Main può accettare come parametro un array di stringhe: String[] (o string[]). Questa tabella contiene gli argomenti della riga di comando utilizzati per avviare l'applicazione. Ad esempio, se eseguiamo il programma P con il seguente comando (Dos):


        P arg0 arg1 … argn

e se Main è dichiarata come segue:

public static void Main(string[] args)

args[0]="arg0", args[1]="arg1" ... Ecco un esempio:


using System;
 
namespace Chap1 {
    class P10 {
        static void Main(string[] args) {
             // list parameters received
            Console.WriteLine("Il y a  " + args.Length + " arguments");
            for (int i = 0; i < args.Length; i++) {
                Console.Out.WriteLine("arguments[" + i + "]=" + args[i]);
            }
        }
    }
}

Per passare argomenti al codice eseguito, procedere come segue:

  • in [1]: clicca con il tasto destro del mouse sul progetto / Proprietà
  • in [2]: scheda [Debug]
  • in [3]: inserire gli argomenti

L'esecuzione dà i seguenti risultati:

1
2
3
4
5
Il y a  4 arguments
arguments[0]=a0
arguments[1]=a1
arguments[2]=a2
arguments[3]=a3

Si noti che il

public static void Main()

è valido se Main non richiede parametri.

3.8. Enumerazioni

Un'enumerazione è un tipo di dati il cui dominio dei valori è un insieme di costanti intere. Consideriamo un programma che deve gestire i voti di un esame. Ce ne sarebbero cinque: Fair, AssezBien, Fine, TrèsBien, Excellent.

Potremmo quindi definire un'enumerazione per queste cinque costanti:


        enum Mentions { Passable, AssezBien, Bien, TrèsBien, Excellent };

Internamente, queste cinque costanti sono codificate da numeri interi consecutivi a partire da 0 per la prima costante, 1 per la successiva, ecc... Una variabile può essere dichiarata in modo da assumere questi valori nell'enumerazione:


            // une variable qui prend ses valeurs dans l'énumération Mentions
Mentions maMention = Mentions.Passable;

Una variabile può essere confrontata con i vari valori possibili nell'enumerazione:


            if (maMention == Mentions.Passable) {
                Console.WriteLine("Peut mieux faire");
}

È possibile ottenere tutti i valori dell'enumerazione:


             // list of statements in string form
            foreach (Mentions m in Enum.GetValues(maMention.GetType())) {
                Console.WriteLine(m);
}

Allo stesso modo in cui il tipo semplice int è equivalente a System.Int32, il tipo semplice enum è equivalente a System.Enum. Questa struttura dispone di un metodo statico GetValues che consente di ottenere tutti i valori di un tipo enumerato passato come parametro. Questo deve essere un oggetto di tipo Type, che è una classe di informazioni sul tipo di dati. Il tipo di una variabile v si ottiene con v.GetType(). Il tipo di tipo T si ottiene con typeof(T). Quindi qui maMention.GetType() restituisce l'oggetto Type dell'enumerazione Mentions e Enum.GetValues(maMention.GetType()) restituisce l'elenco dei valori dell'enumerazione Mentions.

Se ora scriviamo


             //list of mentions in integer form
            foreach (int m in Enum.GetValues(typeof(Mentions))) {
                Console.WriteLine(m);
}

Riga 2: la variabile del ciclo è di tipo intero. Otteniamo quindi l'elenco dei valori di enumerazione in forma intera. L'oggetto di tipo System.Type corrispondente al tipo di dati Mentions viene ottenuto tramite typeof(Mentions). Avremmo potuto scrivere maMention.GetType().

Il programma seguente illustra quanto appena scritto:


using System;
 
namespace Chap1 {
    class P11 {
        enum Mentions { Passable, AssezBien, Bien, TrèsBien, Excellent };
        static void Main(string[] args) {
             // a variable that takes its values from the Mentions enumeration
            Mentions maMention = Mentions.Passable;
             // variable value display
            Console.WriteLine("mention=" + maMention);
             // test with enumeration value
            if (maMention == Mentions.Passable) {
                Console.WriteLine("Peut mieux faire");
            }
             // list of statements in string form
            foreach (Mentions m in Enum.GetValues(maMention.GetType())) {
                Console.WriteLine(m);
            }
             //list of mentions in integer form
            foreach (int m in Enum.GetValues(typeof(Mentions))) {
                Console.WriteLine(m);
            }
        }
    }
}

I risultati sono i seguenti:

mention=Passable
Peut mieux faire
Passable
AssezBien
Bien
TrèsBien
Excellent
0
1
2
3
4

3.9. Passaggio dei parametri a una funzione

Qui ci interessa il modo in cui i parametri vengono passati a una funzione. Consideriamo la seguente funzione statica:


        private static void ChangeInt(int a) {
            a = 30;
            Console.WriteLine("Paramètre formel a=" + a);
}

Nella definizione della funzione, alla riga 1, a è chiamato parametro formale. È presente solo allo scopo di definire la funzione changeInt. Avrebbe potuto benissimo chiamarsi b. Consideriamo ora un utilizzo di questa funzione:


        public static void Main() {
            int age = 20;
            ChangeInt(age);
            Console.WriteLine("Paramètre effectif age=" + age);
}

Qui, nell'istruzione alla riga 3, ChangeInt(età), età è il parametro effettivo che trasmetterà il suo valore al parametro formale a. Ci interessa capire come un parametro formale recuperi il valore di un parametro effettivo.

3.9.1. Passaggio per valore

L'esempio seguente mostra che, per impostazione predefinita, i parametri delle funzioni vengono passati per valore, ovvero il valore del parametro effettivo viene copiato nel parametro formale corrispondente. Abbiamo due entità distinte. Se la funzione modifica il parametro formale, il parametro effettivo rimane invariato.


using System;
 
namespace Chap1 {
    class P12 {
        public static void Main() {
            int age = 20;
            ChangeInt(age);
            Console.WriteLine("Paramètre effectif age=" + age);
        }
        private static void ChangeInt(int a) {
            a = 30;
            Console.WriteLine("Paramètre formel a=" + a);
        }
    }
}

I risultati sono i seguenti:

Paramètre formel a=30
Paramètre effectif age=20

Il valore 20 del parametro effettivo age è stato copiato nel parametro formale a (riga 10). Questo è stato poi modificato (riga 11). Il parametro effettivo è rimasto invariato. Questa modalità è adatta per i parametri di input delle funzioni.

3.9.2. Passaggio per riferimento

In un'esecuzione per riferimento, il parametro effettivo e il parametro formale sono la stessa entità. Se la funzione modifica il parametro formale, viene modificato anche il parametro effettivo. In C#, entrambi devono essere preceduti dalla parola chiave ref :

Ecco un esempio:


using System;
 
namespace Chap1 {
    class P12 {
        public static void Main() {
             // example 2
            int age2 = 20;
            ChangeInt2(ref age2);
            Console.WriteLine("Paramètre effectif age2=" + age2);
        }
        private static void ChangeInt2(ref int a2) {
            a2 = 30;
            Console.WriteLine("Paramètre formel a2=" + a2);
        }
    }
}

e risultati delle prestazioni:

Paramètre formel a2=30
Paramètre effectif age2=30

Il parametro effettivo ha seguito la modifica del parametro formale. Questa modalità è adatta per i parametri di output delle funzioni.

3.9.3. Passaggio per riferimento con la parola chiave out

Consideriamo l'esempio precedente in cui la variabile age2 non verrebbe inizializzata prima di chiamare la funzione changeInt:


using System;
 
namespace Chap1 {
    class P12 {
        public static void Main() {
             // example 2
            int age2;
            ChangeInt2(ref age2);
            Console.WriteLine("Paramètre effectif age2=" + age2);
        }
        private static void ChangeInt2(ref int a2) {
            a2 = 30;
            Console.WriteLine("Paramètre formel a2=" + a2);
        }
    }
}

Quando compiliamo questo programma, otteniamo un errore:

    Use of unassigned local variable 'age2'

Possiamo aggirare questo ostacolo assegnando un valore iniziale a age2. È anche possibile sostituire la parola chiave ref con la parola chiave out. In questo modo indichiamo che il parametro è solo un parametro di output e quindi non necessita di un valore iniziale:


using System;
 
namespace Chap1 {
    class P12 {
        public static void Main() {
             // example 3
            int age3;
            ChangeInt3(out age3);
            Console.WriteLine("Paramètre effectif age3=" + age3);
        }
        private static void ChangeInt3(out int a3) {
            a3 = 30;
            Console.WriteLine("Paramètre formel a3=" + a3);
        }
    }
}

I risultati sono i seguenti:

Paramètre formel a3=30
Paramètre effectif age3=30