1.3. Un programme minimal en C#



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

1.Une classe suffit

1.1 Syntaxe d'une classe exécutable
1.2 Exemples d'applications à une classe


2. Les méthodes sont des fonctions

    2.1 Méthode élémentaire de classe
    2.2 Déclaration d'une méthode
    2.3 Appel d'une méthode


3.Transmission des paramètres en C#

    3.1 Compatibilité des types des paramètres
    3.2 Les trois modes de transmission des paramètres
             *  par valeur
             *  par référence
             *  par résultat
    3.3 Les retours de résultat de méthode-fonction


4. Visibilité des variables

    4.1 Visibilité de bloc
    4.2 Variables dans une classe, dans une méthode
    4.3 Variables dans un bloc autre qu'une classe ou une méthode


Avant d'utiliser les possibilités offertes par les classes et les objets en C#, apprenons à utiliser et exécuter des applications simples C# ne nécessitant pas la construction de nouveaux objets.

Comme C# est un langage entièrement orienté objet, un programme C# est composé de une ou plusieurs classes, nous nous limiterons ici à une seule classe.
 

1.Une classe suffit
 

On peut très grossièrement assimiler un programme C# ne possédant qu'une seule classe, à un programme principal classique d'un langage de programmation algorithmique.
 


 

1.1 Syntaxe d'une classe exécutable

Exemple1 de classe minimale :
class Exemple1 {  }

Cette classe ne fait rien et ne produit rien.

En fait, une classe quelconque peut s'exécuter toute seule à condition qu'elle possède dans ces déclarations internes la méthode Main qui sert à lancer l'exécution de la classe (fonctionnement semblable au lancement d'un programme principal).

Exemple2 de squelette d'une classe minimale exécutable :
class Exemple2
{
    static void Main(string[ ] args)
   { // c'est ici que vous écrivez votre programme principal
    }
}

Exemple3 trivial d'une classe minimale exécutable :
class Exemple3
{
    static void Main(string[ ] args)
   { System.Console.WriteLine("Bonjour !");
    }
}

 

1.2 Exemples d'applications à une classe

Nous reprenons deux exemples de programme utilisant la boucle for, déjà donnés au chapitre sur les instructions, cette fois-ci nous les réécrirons sous la forme d'une application exécutable.

Exemple1
class Application1
{
   static void Main(string[ ] args)
   { /* inverse d'une suite de caractère dans un tableau par
       permutation des deux extrêmes */
    char [ ] Tablecar ={'a','b','c','d','e','f'} ;
    int i, j ;
    System.Console.WriteLine("tableau avant : " + new string(Tablecar));
    for ( i = 0 , j = 5 ;  i<j ; i++ , j-- )
     { char car ;
       car = Tablecar[i];
       Tablecar[i ]= Tablecar[j];
       Tablecar[j] = car;
     }
    System.Console.WriteLine("tableau après : " + new string(Tablecar));
  }
}

L'instruction "new string(Tablecar)" sert uniquement pour l'affichage, elle crée une string à partir du tableau de char.

Contrairement à java il n'est pas nécessaire de sauvegarder la classe dans un fichier qui porte le même nom, tout nom de fichier est accepté à condition que le suffixe soit cs,  ici "AppliExo1.cs". Lorsque l'on demande la compilation (production du bytecode) de ce fichier source  "AppliExo1.cs" le fichier cible produit en bytecode MSIL se dénomme "AppliExo1.exe", il est alors prêt à être exécuté par la machine virtuelle du CLR.

Le résultat de l'exécution de ce programme est le suivant :
tableau avant : abcdef
tableau après : fedcba

Exemple2
class Application2
{
   static void Main(string[ ] args)
   { // recherche séquentielle dans un tableau
     int [ ] table= {12,-5,7,8,-6,6,4,78,2};
     int elt = 4, i ;
     for ( i = 0 ; i<8 ; i++ )
       if (elt= =table[i]) break ;
     if (i = = 8) System.Console.WriteLine("valeur : "+elt+" pas trouvée.");
     else System.Console.WriteLine("valeur : "+elt+" trouvée au rang :"+i);
   }
}

Après avoir sauvegardé la classe dans un fichier xxx.cs, ici "AppliExo2.cs", la compilation de ce fichier "AppliExo2.cs" produit le fichier "AppliExo2.exe" prêt à être exécuté par la machine virtuelle du CLR.

Le résultat de l'exécution de ce programme est le suivant :
valeur : 4 trouvée au rang :6

Conseil de travail :
reprenez tous les exemples simples du chapitre sur les instructions de boucle et le switch en les intégrant dans une seule classe (comme nous venons de le faire avec les deux exemples précédents) et exécutez votre programme.

Remonter


2. Les méthodes sont des fonctions
 

Les méthodes ou bien fonctions représentent une encapsulation des instructions qui déterminent le fonctionnement d'une classe. Sans méthodes pour agir une classe ne fait rien de particulier, dans ce cas elle ne fait que contenir des attributs.
 
 

2.1 Méthode élémentaire de classe

Bien que C# distingue deux sortes de méthodes : les méthodes de classe et les méthodes d'instance, pour l'instant dans cette première partie nous décidons à titre pédagogique et simplificateur de n'utiliser que les méthodes de classe, le chapitre sur C# et la programmation orientée objet apportera les compléments adéquats.
 
Une méthode de classe commence obligatoirement par le mot clef    static.

Donc par la suite dans ce document lorsque nous emploierons le mot méthode sans autre adjectif, il s'agira d'une méthode de classe, comme nos application ne possèdent qu'une seule classe, nous pouvons assimiler ces méthodes aux fonctions de l'application et ainsi retrouver une utilisation classique de C# en mode appplication.

Attention, il est impossible en C#  de déclarer une méthode à l'intérieur d'une autre méthode comme en pascal; toutes les méthodes sont au même niveau de déclaration : ce sont les méthodes de la classe !
 
 

2.2 Déclaration d'une méthode

La notion de fonction en C# est semblable à celle de Java, elle comporte une en-tête avec des paramètres formels et un corps de fonction ou de méthode qui contient les instructions de la méthode qui seront exécutés lors de son appel. La déclaration et l'implémentation doivent être consécutives comme l'indique la syntaxe ci-dessous :

Syntaxe :

corps de fonction :

Nous dénommons en-tête de fonction la partie suivante :
<qualificateurs><type du résultat><nom de fonction> (<liste paramètres formels>)

Sémantique :


Exemples d'en-tête de méthodes sans paramètres en C#
int calculer( ){.....} renvoie un entier de type int
bool tester( ){.....} renvoie un entier de type boolean
void uncalcul( ){.....} procédure ne renvoyant rien

Exemples d'en-tête de méthodes avec paramètres en C#
int calculer(byte a, byte b, int x )
{.....}
fonction à 3 paramètres
bool tester( int k)
{.....}
fonction à 1 paramètre
void uncalcul(int x, int y, int z )
{.....}
procédure à 3 paramètres

 

2.3 Appel d'une méthode

L'appel de méthode en C# s'effectue très classiquement avec des paramètres effectifs dont le nombre doit obligatoirement être le même que celui des paramètres formels et le type doit être soit le même, soit un type compatible ne nécessitant pas de transtypage.

Exemple d'appel de méthode-procédure sans paramètres en C#
class Application3
     {
        static void Main(string[ ] args)
        { 
               afficher( );
        }
       static void afficher( )
      {
          System.Console.WriteLine("Bonjour");
       }
     }
Appel de la méthode afficher

Exemple d'appel de méthode-procédure avec paramètres de même type en C#
class Application4
{  static void Main(string[ ] args)
     { // recherche séquentielle dans un tableau
        int [ ] table= {12,-5,7,8,-6,6,4,78,2};
        long elt = 4;
        int i ;
        for ( i = 0 ; i<=8 ; i++ )
        if (elt= =table[i]) break ;
        afficher(i,elt);
      }
   static void afficher (int rang , long val)
   { if (rang == 8)
        System.Console.WriteLine("valeur : "+val+" pas trouvée.");
      else
        System.Console.WriteLine("valeur : "+val+" trouvée au rang :"+ rang);
   }
}
Appel de la méthode afficher
afficher(i,elt);
Les deux paramètres effectifs "i" et "elt"  sont du même type que le paramètre formel associé. 

- Le paramètre effectif  "i" est associé au paramètre formel rang.

- Le paramètre effectif "elt" est associé au paramètre formel val.

Remonter



3. Transmission des paramètres en C#

Rappelons tout d'abord quelques principes de base :

Dans tous les langages possédant la notion de sous-programme (ou fonction ou procédure), il se pose une question à savoir : à quoi servent les paramètres formels ? Les paramètres formels décrits lors de la déclaration d'un sous-programme ne sont que des variables muettes servant à expliquer le fonctionnement du sous-programme sur des futures variables lorsque le sous-programme s'exécutera effectivement. 

La démarche en informatique est semblable à celle qui, en mathématiques, consiste à écrire  la fonction f(x) = 3*x - 7, dans laquelle x  est une variable muette indiquant comment f est calculée : en informatique elle joue le rôle du paramètre formel. Lorsque l'on veut obtenir une valeur effective de la fonction mathématique f, par exemple pour x=2, on écrit f(2) et l'on calcule f(2)=3*2 - 7 = -1. En informatique on "passera" un paramètre effectif dont la valeur vaut 2 à la fonction. D'une manière générale, en informatique, il y a un sous-programme appelant et un sous-programme appelé par le sous-programme appelant.

 

3.1 Compatibilité des types des paramètres

Resituons la compatibilité des types entier et réel en C#. Un moyen mémotechnique pour retenir cette compatibilité est indiqué dans les figures ci-dessous, par la taille en nombre décroissant de bits de chaque type que l'on peut mémoriser sous la forme "qui peut le plus peut le moins" ou bien un type à n bits acceuillera un sous-type à p bits, si p est inférieur à n.

Compatibilité ascendante des types de variables en C#
(conversion implicite sans transtypage obligatoire)
 
Les types entiers signés  :

 

Les types entiers non signés   :

Les réels  :

Exemple d'appel de la même  méthode-procédure avec paramètres de type compatibles en C#
class Application5
{  static void Main(string[ ] args)
     { // recherche séquentielle dans un tableau
        int [ ] table= {12,-5,7,8,-6,6,4,78,2};
        sbyte elt = 4;
        short i ;
        for ( i = 0 ; i<8 ; i++ )
        if (elt= =table[i]) break ;
        afficher(i,elt);
      }
   static void afficher (int rang , long val)
   { if (rang == 8)
        System.Console.WriteLine("valeur : "+val+" pas trouvée.");
      else
        System.Console.WriteLine("valeur : "+val+" trouvée au rang :"+ rang);
   }
}
Appel de la méthode afficher
afficher(i,elt);
Les deux paramètres effectifs "i" et "elt"  sont d'un type compatible avec celui du paramètre formel associé. 

- Le paramètre effectif  "i" est associé au paramètre formel rang.(short = entier signé sur 16 bits et int = entier signé sur 32 bits)

- Le paramètre effectif "elt" est associé au paramètre formel val.(sbyte = entier signé sur 8 bits et long = entier signé sur 64 bits)
 


 

3.2 Les trois modes principaux de transmission des paramètres
 
passage par valeur
passage par réfférence
passage par résultat

Un paramètre effectif transmis au sous-programme appelé est en fait un moyen d’utiliser ou d’accéder à une information appartenant au bloc appelant (le bloc appelé peut être le même que le bloc appelant, il s’agit alors de récursivité).


 
La question technique qui se pose en C# comme dans tout langage de programmation est de connaître le fonctionnement du passage des paramètres :


En C#, ces trois modes de transmission (ou de passage) des paramètres (très semblables à Delphi) sont implantés.



1 - Les paramètres C#  passés par valeur

Le passage par valeur est valable pour tous les types élémentaires (int, byte, short, long, boolean, double, float, char) et les objets.

En C#  tous les paramètres sont passés par défaut par valeur (lorsque le paramètre est un objet, c'est en fait la référence de l'objet qui est passée par valeur). Pour ce qui est de la vision algorithmique de C#,  le passage par valeur permet à une variable d'être passée comme paramètre d'entrée.
 
static int methode1(int a , char b) { 
 //.......
  return a+b;
}

Cette méthode possède 2 paramètres a et b en entrée passés par valeur et renvoie un résultat de type int.



2 - Les paramètres C#  passés par référence

Le passage par référence est valable pour tous les types de C#.

En C# pour indiquer un  passage par référence on précède la déclaration du paramètre formel du mot clef ref :
 
static int methode1(int a , ref char b) { 
 //.......
  return a+b;
}

Lors de l'appel d'un paramètre passé par référence, le mot clef ref doit obligatoirement précéder le paramètre effectif qui doit obligatoirement avoir été initialisé auparavant :
 
    int x = 10, y = '$', z = 30;
    z = methode1(x, ref y) ;



3 - Les paramètres C#  passés par résultat

Le passage par résultat est valable pour tous les types de C#.

En C# pour indiquer un  passage par résultat on précède la déclaration du paramètre formel du mot out :
 
static int methode1(int a , out char b) {
 //.......
  return a+b;
}

Lors de l'appel d'un paramètre passé par résultat,  le mot clef out doit obligatoirement précéder le paramètre effectif qui n'a pas besoin d'avoir été initialisé :
 
    int x = 10, y , z = 30;
    z = methode1(x, out y) ;

Remarque :
Le choix de passage selon les types élimine les inconvénients dûs à l'encombrement mémoire et à la lenteur de recopie de la valeur du paramètre par exemple dans un passage par valeur, car nous verrons plus loin que les tableaux en C#  sont des objets et que leur structure est passée par référence.

 

3.3 Les retours de résultat de méthode-fonction

Les méthodes en C#  peuvent renvoyer un résultat de n'importe quel type. Bien qu'une méthode ne puisse renvoyer qu'un seul résultat, l'utilisation des passages par référence ou par résultat permet aussi d'utiliser les paramètres d'une méthode comme des variables d'entrée/sortie.

En C#   le retour de résultat d’une méthode fonction, est passé grâce au mot clef return placé n'importe où dans le corps de la méthode.

Syntaxe :

Sémantique :


Exemple la fonction f(x)=3x-7
class Application6
{  static void Main(string[ ] args)
     { // ...
        int x , y ;
        x = 4 ;
        y = f(5) ;
        y = f(x) ;
        System.Console.WriteLine("f(x)="+ f(x) );
        System.Console.WriteLine("f(5)="+ f(5) );
      }
   static int f (int x )
   {  return 3*x-7;
   }
}
Appel de la méthode f
f ( 5 ) ;
ou bien
f ( x ) ;
Dans les deux cas la valeur 5 ou la valeur 4 de x est recopiée dans la zone de pile de la machine virtuelle C#

Exemple de méthode-procédure
class Application7
{  static void Main(string[ ] args)
    
        int a = 0  ;
        procedure ( a );
        procedure ( a+4 );
      }
   static void procedure(int x)
    {
      if (x = = 0)
      { System.Console.WriteLine("avant return");
          return ;
       }
      System.Console.WriteLine("après return");
    }
}
Appel de la méthode procedure

Dans le cas du premier appel (x ==0) est true donc ce sont les instructions:
System.out.println("avant return");
return ;
qui sont exécutées, puis interruption de la méthode.

Dans le cas du second appel (x ==0) est false c'est donc l'instruction:
System.out.println("avant return");
qui est exécutée, puis fin normale de la méthode.
 

Remonter



4. Visibilité des variables

Le principe de base est que les variables en C# sont visibles (donc utilisables) dans le bloc dans lequel elles ont été définies.
 

4.1 Visibilité de bloc

C# est un langage à structure de blocs ( comme pascal et C ) dont le principe général de visibilité est :
Toute variable déclarée dans un bloc est visible dans ce bloc et dans tous les blocs imbriqués dans ce bloc.

En C#  les blocs sont constitués par :
  • les classes,
  • les méthodes,
  • les instructions composées,
  • les corps de boucles,
  • les try...catch

Le masquage des variables n'existe que pour les variables déclarées dans des méthodes :
Il est interdit de redéfinir une variable déjà déclarée dans une méthode soit :
  • comme paramètre de la méthode,
  • comme variable locale à la méthode.
Il est possible de redéfinir  une variable déjà déclarée dans une classe.

Il est interdit de redéfinir dans un bloc (if, while, for, do) une variable déjà déclarée dans une bloc englobant cette boucle.
 


 
 

4.2 Variables dans une classe, dans une méthode

Les variables définies (déclarées, et/ou initialisées) dans une classe sont accessibles à toutes les méthodes de la classe, la visibilité peut être modifiée par les qualificateurs public ou private que nous verrons au chapitre C# et POO.

Exemple de visibilité dans une classe
class ExempleVisible1 {
  int a = 10;

  int g (int x )
  {  return  3*x-a
  }

  int f (int x, int a )
  {  return 3*x-a
  }

}

 La variable "a" définie dans int a =10;  :

- Est une variable de la classe ExempleVisible.

- Elle est visible dans la méthode g et dans la méthode f. C'est elle qui est utilisée dans la méthode g pour évaluer l'expression 3*x-a.

- Dans la méthode f, elle est masquée par le paramètre du même nom qui est utilisé pour évaluer l'expression 3*x-a.
 

Contrairement à ce que nous avions signalé plus haut nous n'avons pas présenté un exemple fonctionnant sur des méthodes de classes (qui doivent obligatoirement être précédées du mot clef static), mais sur des méthodes d'instances dont nous verrons le sens plus loin au chapitre C# et POO. Remarquons avant de présenter le même exemple cette fois-ci sur des méthodes de classes, que quelque soit le genre de méthode utilisée, la visibilité des variables est identique.

Exemple identique sur des méthodes de classe
class ExempleVisible2 {
  static int a = 10;

  static int g (int x )
  {  return  3*x-a
  }

  static int f (int x, int a )
  {  return 3*x-a
  }

}

 La variable "a" définie dans static int a =10;  :

- Est une variable de la classe ExempleVisible.

- Elle est visible dans la méthode g et dans la méthode f. C'est elle qui est utilisée dans la méthode g pour évaluer l'expression 3*x-a.

- Dans la méthode f, elle est masquée par le paramètre du même nom qui est utilisé pour évaluer l'expression 3*x-a.
 

Les variables définies dans une méthode (de classe ou d'instance) subissent les règles classiques de la visibilité du bloc dans lequel elles sont définies :
 
Elles sont visibles dans toute la méthode et dans tous les blocs imbriqués dans cette méthode et seulement à ce niveau (les autres méthodes de la classe ne les voient pas), c'est pourquoi on emploie aussi le terme de variables locales à la méthode.

Reprenons l'exemple précédent en y adjoignant des variables locales aux deux méthodes f et g.

Exemple de variables locales
class ExempleVisible3 {
  static int a = 10;

  static int g (int x )
  {  char car = 't'; 
      long a = 123456;
       ....
      return  3*x-a
  }

  static int f (int x, int a )
  {  char car ='u';
       ....
      return 3*x-a
  }

}

 La variable de classe "a" définie dans static int a = 10; est masquée dans les deux méthodes f et g.

Dans la méthode g, c'est la variable locale longa = 123456 qui masque la variable de classe static int a. char car ='t'; est une variable locale à la méthode g.

- Dans la méthode f, char car ='u'; est une variable locale à la méthode f, le paramètre inta masque la variable de classe static int a.

Les variables locales char car n'existent que dans la méthode où elles sont définies, les variables "car" de f et celle de g n'ont aucun rapport entre elles, bien que portant le même nom.
 


 
 

4.3 Variables dans un bloc autre qu'une classe ou une méthode

Les variables définies dans des blocs du genre instructions composées, boucles, try..catch ne sont visibles que dans le bloc et ses sous-blocs imbriqués, dans lequel elle sont définies.

Toutefois attention aux redéfinitions de variables locales. Les blocs du genre instructions composées, boucles, try..catch ne sont utilisés qu'à l'intérieur du corps d'une méthode (ce sont les actions qui dirigent le fonctionnement de la méthode), les variables définies dans de tels blocs sont automatiquement considérées par C# comme des variables locales à la méthode. Tout en respectant à l'intérieur d'une méthode le principe de visibilité de bloc, C# n'accepte pas le masquage de variable à l'intérieur des blocs imbriqués.

Nous donnons des exemples de cette visibilité :

Exemple correct de variables locales
class ExempleVisible4 {

static int a = 10, b = 2;

  static int f (int x )

  {  char car = 't'; 

      for (int i = 0;  i < 5 ;  i++)
      {int a=7;

         if (a < 7)
          {int b = 8;
           b = 5-a+i*b;
          }

       else b = 5-a+i*b;
       }

       return  3*x-a+b
  }
}
 


La variable de classe "a" définie dans static int a = 10; est masquée dans la méthode f  dans le bloc imbriqué for.

La variable de classe "b" définie dans static int b = 2; est masquée dans la méthode f  dans le bloc imbriqué if.

Dans l'instruction { intb = 8; b = 5-a+i*b; } , c'est la variable b interne à ce bloc qui est utilisée car elle masque la variable b de la classe.

Dans l'instruction else b = 5-a+i*b; , c'est la variable b de la classe qui est utilisée (car la variable int b = 8 n'est plus visible ici) .
 

Exemple de variables locales générant une erreur
class ExempleVisible5 {

static int a = 10, b = 2;

  static int f (int x )

  {  char car = 't'; 

      for (int i = 0;  i < 5 ;  i++)
      {int a=7;

         if (a < 7)
          {int b = 8, a = 9;
           b = 5-a+i*b;
          }

       else b = 5-a+i*b;
       }

       return  3*x-a+b
  }
}
 

Toutes les remarques précédentes restent valides puisque l'exemple ci-contre est quasiment identique au précédent. Nous avons seulement rajouté dans le bloc if la définition d'une nouvelle variable interne a à ce bloc.

C#  produit une erreur de compilation int b = 8, a = 9; sur la variable a, en indiquant que c'est une redéfinition de variable à l'intérieur de la méthode f, car nous avions déjà défini une variable a  ({ int a=7;...) dans le bloc englobant for {...}.
 

Remarquons que le principe de visiblité des variables adopté en C# est identique au principe inclus dans tous les langages à structures de bloc y compris pour le masquage, s'y rajoute en C# uniquement l'interdiction de la redéfinition à l'intérieur d'une même méthode (semblable en fait, à l'interdiction de redéclaration sous le même nom, de variables locales à un bloc).

Toutefois le compilateur C# n'accepte pas que l'on utilise une variable de classe qui a été redéfinie dans un bloc interne à une méthode, sans la qualifier par la notation uniforme aussi appelée opération de résolution de portée. Les 6 exemples ci-dessous situent le discours, observez les utilisations et les redéfinitions correctes de  la variable  "static int  b = 2;"  déclarée comme une variable de classe et redéfinie dans différents blocs de la classe (les variables d'un même bloc ont la même couleur, ceci  permet de visualiser rapidement quelle est la variable utilisée, lorsqu'il y a un conflit signalé par le compilateur sur une instruction en italique, nous avons mis tout de suite après le code correct accepté par le compilateur) :
 
class ClasseA {
  static int a = 10, b = 2;
  static int f (int x )
  {  char car = 't'; 
      for (int i = 0;  i < 5 ;  i++)
      {int a=7;
         if (a < 7)
          {
           b = 5-a+i*b;
          }
          else b = 10-a+i*b;
       }
      return  3*x-a+b
  }

}

class ClasseA {
  static int a = 10, b = 2;
  static int f (int x )
  {  char car = 't'; 
      int b = 8;
      for (int i = 0;  i < 5 ;  i++)
      {int a=7;
         if (a < 7)
          {
           b = 5-a+i*b;
          }
         else b = 10-a+i*b;
       }
      return  3*x-a+b
  }
}
class ClasseA {
  static int a = 10, b = 2;
  static int f (int x )
  {  char car = 't'; 
      for (int i = 0;  i < 5 ;  i++)
      {int a=7;
         if (a < 7)
          {int b = 8;
           b = 5-a+i*b;
          }

         else b = 10-a+i*b;
       }
      return  3*x-a+b
  }
}

class ClasseA {
  static int a = 10, b = 2;
  static int f (int x )
  {  char car = 't'; 
      for (int i = 0;  i < 5 ;  i++)
      {int a=7; int b = 8;
         if (a < 7)
          {
           b = 5-a+i*b;
          }

          else b = 10-a+i*b;
       }
      return  3*x-a+b
  }
}

class ClasseA {
  static int a = 10, b = 2;
  static int f (int x )
  {  char car = 't'; 
      for (int i = 0;  i < 5 ;  i++)
      { int a=7;
         if (a < 7)
          {int b = 8;
           b = 5-a+i*b;
          }
       else
         b = 10-a+i*b; <<<  refus  : conflit
           ClasseA.b = 10-a+i*ClasseA.b; <<<  accepté
       }
      return  3*x-a+ClasseA.b
  }
}
class ClasseA {
  static int a = 10, b = 2;
  static int f (int x )
  {  char car = 't'; 
      for (int i = 0;  i < 5 ;  i++)
      { int a=7; int b = 8;
         if (a < 7)
          {
           b = 5-a+i*b;
          }
       else
         b = 10-a+i*b; <<<  refus  : conflit
           ClasseA.b = 10-a+i*ClasseA.b; <<<  accepté
       }
       return  3*x-a+ClasseA.b
  }
}

Là où le compilateur C# détecte un conflit potentiel, il suffit alors de qualifier la variable grâce à l'opérateur de résolution de portée, comme par exemple ClasseA.b pour indiquer au compilateur la variable que nous voulons utiliser.