1.2. C# , les instructions



Plan de ce chapitre:   ...........

Les instructions de base de C# sont identiques syntaxiquement et sémantiquement à celles de Java, le lecteur qui connaît déjà le fonctionnement des instructions en Java peut ignorer ce chapitre et passer au chapitre suivant.

1. instructions-bloc

1.1 Instruction
1.2 Instruction complète
1.3 Bloc-instruction
1.4 Visibilté dans un bloc-instruction


2. L'affectation

    2.1 Affectation simple
    2.2 Raccourcis et opérateurs d'affectation


3. Les conditions

    3.1 les instructions conditionnelles
    3.2 l'opérateur conditionnel


4. Les itérations

    4.1 Itération while
    4.2 Itération do...while
    4.3 Itération for


5.Break et continue

5.1 Interruption break
5.2 Rebouclage continue
 
6. Switch...case
 

7.try...catch
 


Les instructions de base de C# sont identiques syntaxiquement et sémantiquement à celles de Java, le lecteur qui connaît déjà le fonctionnement des instructions en Java peut ignorer ce chapitre.

1. instructions-bloc
 
 Une large partie de la norme ANSI du langage C est reprise dans C#, ainsi que la norme Delphi. 


Dans ce sous-chapitre nous expliquons les instructions C#  en les comparant à pascal-delphi. Voici la syntaxe d'une instruction en C#  :

1.1 instruction :

 

1.2 instruction complète :

Toutes les instructions se terminent donc en C#  par un point-virgule " ; "
 
 

1.3 bloc - instruction composée :
L'élément syntaxique

est aussi dénommé bloc ou instruction composée au sens de la visibilité des variables C# .
 
 

1.4 visibilité dans un bloc - instruction  :

Exemple de déclarations licites et de visibilité dans 3 blocs instruction imbriqués :
int a, b = 12;
   { int x , y = 8 ;
     { int z =12;
            x = z ;
            a = x + 1 ;
        { int u = 1 ;
            y = u - b ;
         }
     }
   }

schéma d'imbrication des 3 blocs

Nous examinons ci-dessous l'ensemble des instructions simples de C# .

Remonter



2. L'affectation

C# est un langage de la famille des langages hybrides, il possède la notion d'instruction d'affectation.

Le symbole d'affectation en C# est  " = ", soit par exemple :
 
x = y ; 
// x doit obligatoirement être un identificateur de variable.

 

2.1 Affectation simple

L'affectation peut être utilisée dans une expression :

soient les instruction suivantes  :
 

int a , b = 56 ;
a = (b = 12)+8 ;  // b prend une nouvelle valeur dans l'expression
a = b = c = d =8 ; // affectation multiple


simulation d'exécution C# :
 instruction  valeur de a   valeur de b
  int a , b = 56 ;   a = ???   b = 56
   a = (b = 12)+8 ;   a = 20   b = 12

 

2.2 Raccourcis et opérateurs d'affectation

Soit op un opérateur  appartenant à l'ensemble des opérateurs suivant 

{ +, - , * , / , % , << , >> , >>> , & , | , ^ }

Il est possible d'utiliser sur une seule variable le nouvel opérateur op= construit avec l'opérateur op.
 
x op= y ;     signifie en fait  :   x = x op y

Il s'agit plus d'un raccourci syntaxique que d'un opérateur nouveau (sa traduction en MSIL est exactement la même : la traduction de a op= b devrait être plus courte en instructions p-code que a = a op b).

Ci-dessous le code MSIL engendré par i = i+5; et i +=5; est effectivement identique :

Code MSIL engendré  pour l'instruction C#  :  i = i + 5 ;
IL_0077:  ldloc.1
IL_0078:  ldc.i4.5
IL_0079:  add
IL_007a:  stloc.1

 Code MSIL engendré  pour l'instruction C#  :  i += 5 ;
IL_007b:  ldloc.1
IL_007c:  ldc.i4.5
IL_007d:  add
IL_007e:  stloc.1

soient les instruction suivantes  : 
int a , b = 56 ;
a = -8 ;
a += b ;  // équivalent à : a = a + b
b *= 3 ;  // équivalent à : b = b * 3


simulation d'exécution C# :
 instruction  valeur de a   valeur de b
  int a , b = 56 ;   a = ???   b = 56
  a = -8 ;   a = -8   b = 56
  a += b ;   a = 48   b = 56
  b *= 3 ;   a = 48   b = 168

Remarque :


Ci-dessous nous voyons que le code MSIL engendré par "table[ f(i) ] = table[ f(i) ] +9 ;" et "table[ f(i) ] += 9 ;" n'est pas le même :

1 . Code MSIL engendré pour l'nstruction C# :  table[ f(i) ] = table[ f(i) ] +9
IL_0086:  ldloc.3 // adr(table)
IL_0087:  ldarg.0
IL_0088:  ldloc.1 // adr(i)
IL_0089:  call       instance int32 exemple.WinForm::f(int32)


IL_008e:  ldloc.3
IL_008f:  ldarg.0
IL_0090:  ldloc.1
IL_0091:  call       instance int32 exemple.WinForm::f(int32)


IL_0096:  ldelem.i4
IL_0097:  ldc.i4.s   9
IL_0099:  add                                        //table[ f(i) ] = table[ f(i) ] + 9 ;
IL_009a:  stelem.i4

         Au total, 12 instructions MSIL  dont deux appels : call  instance int32 exemple.WinForm::f(int32)

  2 . Code MSIL engendré pour l'nstruction C# :  table[ f(i) ] += 9
IL_009b:  ldloc.3
IL_009c:  dup
IL_009d:  stloc.s    CS$00000002$00000000
IL_009f:  ldarg.0
IL_00a0:  ldloc.1
IL_00a1:  call       instance int32 exemple.WinForm::f(int32)

IL_00a6:  dup
IL_00a7:  stloc.s    CS$00000002$00000001
IL_00a9:  ldloc.s    CS$00000002$00000000
IL_00ab:  ldloc.s    CS$00000002$00000001

IL_00ad:  ldelem.i4
IL_00ae:  ldc.i4.s   9
IL_00b0:  add
IL_00b1:  stelem.i4

Au total, 14 instructions MSIL  dont un seul appel : call  instance int32 exemple.WinForm::f(int32)
Dans l'exemple qui précède, il y a réellement gain sur le temps d'exécution de l'instruction table[ f(i) ] += 9, si le temps d'exécution de l'appel à f(i) à travers l'instruction MSIL < call instance int32 exemple.WinForm::f(int32) > , est significativement long devant les temps d'exécution des opérations ldloc et stloc.

En fait d'une manière générale en C# comme dans les autres langages, il est préférable d'adopter l'attitude prise en Delphi qui consiste à encourager la lisibilité du code en ne cherchant pas à écrire du code le plus court possible. Dans notre exemple précédent,  la simplicité consisterait à utiliser une variable locale x et à stocker la valeur de f(i) dans cette variable :


table[ f(i) ] = table[ f(i) ] + 9 ;
x = table[ f(i) ] ;
table[ x ] = table[ x ] + 9 ;


Ces deux écritures étant équivalentes seulement si f(i) ne contient aucun effet de bord !

Info MSIL :
ldloc : Charge la variable locale à un index spécifique dans la pile d'évaluation.
stloc : Dépile la pile d'évaluation et la stocke dans la liste de variables locales à un index spécifié.


Remonter



3. Les conditions
 

3.1 les instructions conditionnelles

Syntaxe :


Schématiquement les conditions sont de deux sortes :


La définition de l'instruction conditionnelle de C# est classiquement celle des langages algorithmiques; comme en pascal  l'expression doit être de type booléen (différent du C), la notion d'instruction a été définie plus haut.

Exemple d'utilisation du if..else (comparaison avec pascal)
Pascal-Delphi C# 
var a , b , c : integer ;
....
if  b=0 then c := 1
else begin
c := a / b;
writeln("c = ",c);
end;
c := a*b ;
if c <>0 then c:= c+b
else c := a
int a , b , c ;
....
if ( b = = 0 ) c =1 ;
else {
c = a / b;
System.Console.WriteLine("c = " + c);
}
if ((c = a*b) != 0) c += b;
else c = a;

Remarques :


L'instruction ambigüe :
if ( Expr1 ) if ( Expr2 ) InstrA ; else InstrB  ;
 
Le compilateur résoud l'ambigüité ainsi :
if ( Expr1 ) if ( Expr2 ) InstrA ; else InstrB  ;


Exemple de parenthésage du else pendant
Pascal-Delphi C# 
if  Expr1  then
begin 
  if  Expr2  then InstrA 
end
else InstrB 
if ( Expr1 ) {
     if ( Expr2 ) InstrA ; 
}
else InstrB 

 
 

3.2 l'opérateur conditionnel

Il s'agit ici comme dans le cas des opérateurs d'affectation d'une sorte de raccourci entre l'opérateur conditionnel if...else et l'affectation. Le but étant encore d'optimiser le bytecode engendré.

Syntaxe :

Où expression est une expression renvoyant une valeur booléenne (le test), valeur sont des expressions genérales (variable, expression numérique, boolénne etc...) renvoyant une valeur de type T.

Sémantique :
Exemple :

int a,b,c ;
c =   a = = 0  ?  b  a+1 ;
Si l'expression est true l'opérateur renvoie la première valeur, (dans l'exemple c vaut la valeur de b)
Si l'expression est false l'opérateur renvoie la seconde valeur (dans l'exemple c vaut la valeur de a+1).


Sémantique de l'exemple avec un if..else :

if (a = = 0) c = b; else c = a+1;


1 . Code MSIL engendré pour l'instruction C#  : c =   a = = 0  ?  b  :  a+1 ;

IL_0007:  ldloc.0
IL_0008:  brfalse.s  IL_000f
IL_000a:  ldloc.0
IL_000b:  ldc.i4.1
IL_000c:  add
IL_000d:  br.s       IL_0010
IL_000f:  ldloc.1
IL_0010:  stloc.2  
 

une seule opération de stockage pour c : IL_0010:  stloc.2

2 . Code MSIL engendré pour l'instruction C#  : if (a = = 0) c = b; else c = a+1 ;

IL_0011:  ldloc.0
IL_0012:  brtrue.s   IL_0018
IL_0014:  ldloc.1
IL_0015:  stloc.2
IL_0016:  br.s       IL_001c
IL_0018:  ldloc.0
IL_0019:  ldc.i4.1
IL_001a:  add
IL_001b:  stloc.2  
 

deux opérations de stockage pour c :

IL_0015:  stloc.2
IL_001b:  stloc.2


Le code MSIL engendré a la même structure classique de code de test pour les deux instructions, la traduction de l'opérateur sera légèrement plus rapide que celle de l'instructions car, il n'y a pas besoin de stocker deux fois le résultat du test dans la variable c (qui ici, est représentée par l'instruction MSIL stloc.2)

question :

utiliser l'opérateur conditionnel pour calculer le plus grand de deux entiers.
 réponse : int a , b , c ; ....
c = a>b ? a : b ;

question :  que fait ce morceau le programme ci-après
int a , b , c ; ....
c = a>b ? (b=a) : (a=b)
 réponse : a,b,c contiennent après exécution le plus grand des deux entiers contenus au départ dans a et b.


Remonter



4. Les itérations

4.1.Itération while

Syntaxe :

Où expression est une expression renvoyant une valeur booléenne (le test de l'itération).

Sémantique :
Identique à celle du pascal (instruction algorithmique tantque .. faire .. ftant) avec le même défaut de fermeture de la boucle.
 

Exemple de boucle while
Pascal-Delphi C#
while  Expr  do Instr while ( Expr ) Instr ;
while  Expr  do
begin 
   InstrA ;
   InstrB ; ...
end
while ( Expr )
{
    InstrA ;
    InstrB ; ...
}

 

4.2.Itération do...while

Syntaxe :

Où expression est une expression renvoyant une valeur booléenne (le test de l'itération).

Sémantique :
"do Instr while ( Expr )" fonctionne comme l'instruction algorithmique répéter  Instr  jusquà non Expr.

Sa sémantique peut aussi être expliquée à l'aide d'une autre instruction C# , le  while( ) :
do Instr while ( Expr ) º  Instr ; while ( Expr ) Instr

Exemple de boucle do...while
Pascal-Delphi C#
repeat
   InstrA ;
   InstrB ; ...
until not Expr
do
{
    InstrA ;
    InstrB ; ...
} while ( Expr )

 

4.3.Itération for

Syntaxe :

Sémantique :
Une boucle for contient 3 expressions for (Expr1 ; Expr2 ; Expr3 ) Instr, d'une manière générale chacune de ces expressions joue un rôle différent dans l'instruction for. Une instruction for en C# (comme en Java) est plus puissante et plus riche qu'une boucle for dans d'autres langages algorithmiques. Nous donnons ci-après une sémantique minimale :
 


"for (Expr1 ; Expr2 ; Expr3 ) Instr" fonctionne au minimum comme l'instruction algorithmique pour... fpour.

Sa sémantique peut aussi être approximativement(*) expliquée à l'aide d'une autre instruction C#  while :
for (Expr1 ; Expr2 ; Expr3 ) Instr ; Expr1
while ( Expr2
{ Instr ;
   Expr3
}
(*)Nous verrons au paragraphe consacré à l'instruction continue que si l'instruction for contient un continue cette définition sémantique n'est pas valide.
 

Exemples montrant la puissance du for
Pascal-Delphi C# 
for i:=1 to 10 do
begin
   InstrA ;
   InstrB ; ...
end
for ( i = 1; i<=10; i++ )
{
    InstrA ;
    InstrB ; ...
i := 10; k := i;
while (i>-450) do
begin
  InstrA ;
  InstrB ; ...
  k := k+i;
  i := i-15;
end
for ( i = 10, k = i ;i>-450 ; k += i ,  i -= 15)
{
    InstrA ;
    InstrB ; ...
i := n;
while i<>1 do
  if i mod 2 = 0 then i := i div 2
  else i := i+1
for ( i = n ;i !=1 ; i % 2 == 0 ? i /=2 : i++);
 // pas de corps de boucle !


Montrons  exemple de boucle for dite boucle infinie :
for ( ; ; );   est équivalente à  while (true);

Voici une boucle ne possédant pas de variable de contrôle(f(x) est une fonction déjà déclarée) :
for (int n=0 ; Math.abs(x-y) < eps ; x = f(x) );

Terminons par une boucle for possédant deux variables de contrôle :
//inverse d'une suite de caractère dans un tableau par permutation des deux extrêmes
char [ ] Tablecar ={'a','b','c','d','e','f'} ;
for ( i = 0 , j = 5 ;  i<j ; i++ , j-- )
{ char car ;
   car = Tablecar[i];
   Tablecar[i ]= Tablecar[j];
   Tablecar[j] = car;
  }
dans cette dernière boucle ce sont les variations de i et de j qui contrôlent la boucle.
 
 

Remarques récapitulatives sur la boucle for en C#  :

Remonter



5. Break et continue
 

5.1.Interruption break

Syntaxe  :

Sémantique :
Une instruction break ne peut se situer qu'à l'intérieur du corps d'instruction d'un bloc switch (le switch est traité au paragraphe suivant), ou de l'une des trois itérations while, do..while, for.

Lorsque break est présente dans l'une des trois itération while, do..while, for :
 


Exemple d'utilisation du break dans un for :
(recherche séquentielle dans un tableau)
   int [ ] table= {12,-5,7,8,-6,6,4,78,2};
   int elt = 4;
   for ( i = 0 ; i<8 ; i++ )
     if (elt= =table[i]) break ;
   if (i = = 8)System.out.println("valeur : "+elt+" pas trouvée.");
   else System.out.println("valeur : "+elt+" trouvée au rang :"+i);

 
Explications


5.2.Rebouclage continue

Syntaxe  :

Sémantique :
Une instruction continue ne peut se situer qu'à l'intérieur du corps d'instruction de l'une des trois itérations while, do..while, for.

Lorsque continue est présente dans l'une des trois itération while, do..while, for :
 


Exemple d'utilisation du continue dans un for :
Rappelons qu'un for s'écrit généralement  : 
for (Expr1 ; Expr2 ; Expr3 ) Instr
L'instruction continue présente dans une telle boucle for s'effectue ainsi :
  • exécution immédiate de Expr3
  • ensuite, exécution de Expr2
  • réexécution du corps de boucle.
int [ ] ta = {12,-5,7,8,-6,6,4,78,2}, tb = new int[8];
for ( i = 0, n = 0 ; i<8i++ , k = 2*n )
{ if ( ta[i] = = 0 ) continue ;
  tb[n] = ta[i];
  n++;
}

 
Explications
Si l'expression ( ta[i] = = 0 ) est true, la suite du corps des instructions de la boucle (tb[n] = ta[i];  n++;) n'est pas exécutée et il y a rebouclage du for .

Le déroulement est alors le suivant :


et la boucle se poursuit en fonction de la valeur de la condition de rebouclage.

Cette boucle recopie dans le tableau d'entiers tb les valeurs non nulles du tableau d'entiers ta.

Attention
Nous avons déjà signalé plus haut que  l'équivalence suivante entre un for et un while
for (Expr1 ; Expr2 ; Expr3 ) Instr º Expr1
while ( Expr2
{ Instr ;
   Expr3
}
valide dans le cas général, était mise en défaut si le corps d'instruction contenait un continue.

Voyons ce qu'il en est en reprenant l'exemple précédent. Essayons d'écrire la boucle while qui lui serait équivalente selon la définition générale. Voici ce que l'on obtiendrait :
for ( i = 0, n = 0 ; i<8i++ , k = 2*n )
{ if ( ta[i] = = 0 ) continue ;
  tb[n] = ta[i];
  n++;
}

i = 0; n = 0 ; 
while ( i<8
{ if ( ta[i] = = 0 ) continue ;
   tb[n] = ta[i];
   n++;
   i++ ; k = 2*n;
}

Dans le while le continue réexécute la condition de rebouclage i<8 sans exécuter l'expression  i++ ; k = 2*n; (nous avons d'ailleurs ici une boucle infinie). Une boucle while équivalente au for précédent pourrait être la suivante :
 for ( i = 0, n = 0 ; i<8i++ , k = 2*n )
{ if ( ta[i] = = 0 ) continue ;
  tb[n] = ta[i];
  n++;
}

 


 i = 0; n = 0 ; 
 while ( i<8
{ if ( ta[i] = = 0 )
  i++ ; k = 2*n;
     continue ;
   }
   tb[n] = ta[i];
   n++;
}

Remonter



6. Switch...case

Syntaxe  :

bloc switch :

Sémantique :

•    La partie expression d'une instruction switch doit être une expression ou une variable du type byte, char, int ou bien short.
•    La partie expression d'un bloc switch doit être une constante ou une valeur immédiate du type byte, char, int ou bien short.
•    switch <Epr1> s'appelle la partie sélection de l'instruction : il y a évaluation de <Epr1> puis selon la valeur obtenue le programme s'exécute en séquence à partir du case contenant la valeur immédiate égal. Il s'agit donc d'un déroutement du programme dès que <Epr1> est évaluée vers l'instruction étiquetée par le case <Epr1> associé.

 
Cette instruction en C#, contrairement à Java, est structurée , elle doit obligatoirement être utilisée avec l'instruction break afin de simuler le comportement de l'instruction structurée case..of du pascal.

 Exemple de switch..case..break
Pascal-Delphi C# 
var x : char ;
....
case x of
 'a' : InstrA;
 'b' : InstrB;
 else InstrElse
end;
char x ;
....
switch (x)
{
    case 'a' : InstrA ; break;
    case 'b' : InstrB ; break;
    default : InstrElse;

Dans ce cas le déroulement de l'instruction switch après déroutement vers le bon case, est interrompu par le break qui renvoie la suite après la fin du bloc switch. Une telle utilisation correspond à une utilisation de if...else imbriqués (donc utilisation structurée) mais devient plus lisible que les if ..else imbriqués, elle est donc fortement conseillée dans ce cas.

Exemples :
C#  - source Résultats de l'exécution
int  x = 10;

switch (x+1)
   { case 11 : System.out.println(">> case 11");
                     break;
      case 12 : System.out.println(">> case 12"); 
                     break;
      default : System.out.println(">> default"); 
   }

>> case 11
int  x = 11;

switch (x+1)
   { case 11 : System.out.println(">> case 11");
                     break;
      case 12 : System.out.println(">> case 12"); 
                     break;
      default : System.out.println(">> default"); 
   }

>> case 12

Il est toujours possible d'utiliser des instructions if … else imbriquées pour représenter un switch avec break.

Programmes équivalents switch et if...else :
C#  - switch C#  - if...else
int  x = 10;

switch (x+1)
   { case 11 : System.out.println(">> case 11");
                     break;
      case 12 : System.out.println(">> case 12"); 
                     break;
      default : System.out.println(">> default"); 
   }

int  x = 10;

if (x+1= = 11)System.out.println(">> case 11");
else
if (x+1= = 12)System.out.println(">> case 12");
else
System.out.println(">> default"); 

 


Bien que la syntaxe du switch …break soit plus contraignante que celle du case…of de Delphi, le fait que cette instruction apporte commme le case…of une structuration du code, conduit à une amélioration du code et augmente sa lisibilité. Lorsque cela est possible, il est donc conseillé de l'utiliser d'une manière générale comme alternative à des if...then…else imbriqués.

Remonter



7. try...catch

Nous reviendrons plus loin sur les exceptions et l'instruction try..catch. Nous donnons ici la syntaxe d'une version simple et nous indiquons que sa sémantique est semblable à celle du bloc try..except de Delphi. Sachons pour l'instant que C# dispose d'un mécanisme puissant identique à Java, présent dans les langages robustes Ada, C++, Delphi pour intercepter et traiter les erreurs ou incidents survenant dans un bloc d'instruction.

L'instruction try...catch sert à traiter de tels incidents, elle dispose d'un bloc try (pour les instructions à protéger)et d'un ou plusieurs blocs catch (pour divers genres de traitement en cas d'incidents).

try {
   BLOC à protéger
  }
   catch (TypeErreur1  Err1) {
      TRAITEMENT d'une erreur du type TypeErreur1
  }
catch (TypeErreur2  Err2) {
      TRAITEMENT d'une erreur du type TypeErreur2
  }
...
catch (TypeErreurX  ErrX) {
      TRAITEMENT d'une erreur du type TypeErreurX
  }

Ci-dessous une exemple permettant d'intercepter une erreur dans la division de l'entier x par l'entier y :

Exemple de try...catch
Pascal-Delphi C# 
var x , y : integer;
try
 x := 1;
 y := 0;
 Memo.lines.add("division ="+x div y);
except
  on Err : EIntError do begin
   Memo.lines.add("Calcul impossible.");
   Memo.lines.add(Err.message);
  end
end;
int x , y ;
try {
    x = 1;
    y = 0;
   System.Console.WriteLine( "division ="+x/y);
  } 
catch (ArithmeticException Err) {
   System.Console.WriteLine("Calcul impossible.");
   System.Console.WriteLine(Err.getMessage());
  }