Les déclarations conditionnelles
Dans cette page, nous aborderons les instructions conditionnelles en C#, que nous pouvons utiliser pour exécuter différentes actions en fonction d'une condition donnée. Nous expliquerons la syntaxe des opérateurs conditionnels if et if-else avec des exemples appropriés et expliquerons l'application pratique de l'opérateur de sélection switch-case.
Nous nous concentrerons sur les meilleures pratiques à suivre afin d'obtenir un meilleur style de programmation lors de l'utilisation d'instructions conditionnelles imbriquées ou d'autres types d'instructions conditionnelles.
Opérateurs de comparaison et expressions booléennes
Dans la section suivante, nous allons rappeler les opérateurs de comparaison de base du langage de programmation C#. Ils sont importants, car nous les utilisons pour décrire les conditions dans nos instructions conditionnelles.
Opérateurs de comparaison
Il existe plusieurs opérateurs de comparaison en C#, étant utilisés pour comparer des paires d'entiers, de nombres à virgule flottante, de caractères, de chaînes et d'autres types :
Opérateur | Action |
---|---|
== | Égal à |
!= | Pas égal à |
> | Supérieur à |
>= | Supérieur ou égal à |
< | Inférieur à |
<= | Inférieur ou égal à |
Les opérateurs de comparaison peuvent être utilisés pour comparer des expressions telles que deux nombres, deux expressions numériques ou un nombre et une variable. Le résultat de la comparaison est une valeur booléenne (vrai ou faux).
Voyons un exemple d'utilisation des comparaisons :
- int Poids = 777;
- Console.WriteLine(Poids >= 500); // True
- char Genre = 'M';
- Console.WriteLine(Genre <= 'F'); // False
- double LongueurOndedeCouleur = 1.630;
- Console.WriteLine(LongueurOndedeCouleur > 1.621); // True
- int a = 5;
- int b = 7;
- bool condition = (b > a) && (a + b < a * b);
- Console.WriteLine(condition); // True
- Console.WriteLine('B' == 'A' + 1); // True
Dans l'exemple de code, nous effectuons une comparaison entre des nombres et entre des caractères. Les nombres sont comparés par taille tandis que les caractères sont comparés par ordre lexicographique (l'opération utilise les nombres Unicode pour les caractères correspondants).
Comme on le voit dans l'exemple, le type de données char se comporte comme un nombre et peut être soustrait, ajouté et comparé à des nombres librement. Cependant, cela doit être utilisé avec prudence car cela pourrait rendre le code difficile à lire et à comprendre. En exécutant l'exemple, nous produirons la sortie suivante :
TrueFalse
True
True
True
En C#, plusieurs types de données peuvent être comparés :
- les nombres (int, long, float, double, ushort, decimal, ...)
- les caractères (char)
- les booléens (bool)
- les références à des objets, également appelés pointeurs d'objet (string, object, talbeau et autres)
Chaque comparaison peut concerner deux nombres, deux valeurs booléennes ou deux références d'objet. Il est permis de comparer des expressions de types différents, comme un entier avec un nombre à virgule flottante par exemple. Cependant, toutes les paires de types de données ne peuvent pas être comparées directement. Par exemple, nous ne pouvons pas comparer une chaîne de caractères avec un nombre.
Comparaison d'entiers et de caractères
Lorsque l'on compare des entiers et des caractères, on compare directement leur représentation binaire en mémoire, c'est-à-dire que l'on compare leurs valeurs. Par exemple, si l'on compare deux nombres de type int, on comparera les valeurs de leurs séries respectives de 4 octets. Voici un exemple de comparaison d'entiers et de caractères :
Le résultat de l'exemple est le suivant :
char 'a' == 'a'? Truechar 'a' == 'b'? False
4 != 7? True
3.0 == 3L? True
true == false? False
Comparaison des références aux objets
Dans .NET Framework, il existe des types de données de référence ne contenant pas leur valeur (contrairement aux types de valeur), mais contiennent l'adresse de la mémoire dans la mémoire de tas où se trouve leur valeur. Les chaînes de caractères, les tableaux et les classes sont de tels types. Ils se comportent comme un pointeur vers une valeur et peuvent avoir la valeur null, c'est-à-dire aucune valeur. Lorsque nous comparons des variables de type référence, nous comparons les adresses qu'elles contiennent, c'est-à-dire que nous vérifions si elles pointent vers le même emplacement dans la mémoire, c'est-à-dire vers le même objet.
Deux pointeurs d'objet (références) peuvent faire référence au même objet ou à des objets différents, ou l'un d'eux peut pointer vers nulle part (avoir une valeur nulle). Dans l'exemple suivant, nous créons deux variables pointant vers la même valeur (objet) dans la mémoire de tas :
Après avoir exécuté le code source ci-dessus, les deux variables chaine et autreChaine pointeront vers le même objet (chaîne de caractères avec la valeur "café"), se trouvant à une certaine adresse dans la mémoire de tas (mémoire de tas géré).
Nous pouvons vérifier si les variables pointent vers le même objet avec l'opérateur de comparaison (==). Pour la plupart des types de référence, cet opérateur ne compare pas le contenu des objets mais vérifie plutôt s'ils pointent vers le même emplacement en mémoire, c'est-à-dire s'il s'agit d'un seul et même objet. Les comparaisons de taille (<, >, <= et >=) ne sont pas applicables aux variables de type objet.
L'exemple suivant illustre la comparaison de références à des objets :
- string chaine = "café";
- string autreChaine = chaine;
- string troisiemeChaine = "caf";
- troisiemeChaine = troisiemeChaine + 'é';
- Console.WriteLine("chaine = {0}", chaine);
- Console.WriteLine("autreChaine = {0}", autreChaine);
- Console.WriteLine("troisiemeChaine = {0}", troisiemeChaine);
- Console.WriteLine(chaine == autreChaine); // True - même objet
- Console.WriteLine(chaine == troisiemeChaine); // True - objets égaux
- Console.WriteLine((object)chaine == (object)autreChaine); // True
- Console.WriteLine((object)chaine == (object)troisiemeChaine); // False
Si nous exécutons l'exemple de code, nous obtiendrons le résultat suivant :
chaine = caféautreChaine = café
troisiemeChaine = café
True
True
True
False
Comme les chaînes de caractères utilisées dans l'exemple (instances de la classe System.String, définie par le mot-clef string en C#) sont de type référence, leurs valeurs sont définies comme des objets dans la mémoire de tas. Les deux objets chaine et troisiemeChaine ont des valeurs égales, mais sont des objets différents, situés à des adresses distinctes dans la mémoire. La variable autreChaine est également de type référence et obtient l'adresse (la référence) de chaine, c'est-à-dire pointe vers l'objet existant chaine. Ainsi, par la comparaison des variables chaine et troisiemeChaine, il apparaît qu'elles sont un seul et même objet et qu'elles sont égales. Le résultat de la comparaison entre chaine et troisiemeChaine est également l'égalité, car l'opérateur == compare les chaînes par valeur et non par adresse (une exception très utile à la règle de comparaison par adresse). Cependant, si nous convertissons les trois variables en objets et que nous les comparons ensuite, nous obtiendrons une comparaison des adresses dans le tas où se trouvent leurs valeurs et le résultat sera différent.
L'exemple ci-dessus montre que l'opérateur == a un comportement spécial lors de la comparaison de chaînes de caractères, mais pour le reste des types de référence (comme les tableaux ou les classes), il applique la comparaison par adresse.
Opérateurs logiques
Rappelons les opérateurs logiques en C#. Ils sont souvent utilisés pour construire des expressions logiques (booléennes). Les opérateurs logiques sont : &&, ||, ! et ^.
Opérateurs logiques && et ||
Les opérateurs logiques && (ET logique) et || (OU logique) ne sont utilisés que sur des expressions booléennes (valeurs de type bool). Pour que le résultat - de la comparaison de deux expressions avec l'opérateur && - soit vrai (true), les deux opérandes doivent avoir la valeur true. Par exemple :
- bool result = (2 < 3) && (3 < 4);
Cette expression est «vraie» car les deux opérandes (2 < 3) et (3 < 4) sont «vrais». L'opérateur logique && est également appelé «court-circuit» car il ne perd pas de temps dans des calculs supplémentaires inutiles. Il évalue la partie gauche de l'expression (le premier opérande) et si le résultat est faux, il ne perd pas de temps à évaluer le deuxième opérande - il n'est pas possible que le résultat final soit «vrai» lorsque le premier opérande n'est pas «vrai». Pour cette raison, il est également appelé «opérateur logique de court-circuit» «et».
De même, l'opérateur || renvoie vrai si au moins l'un des deux opérandes a la valeur «vrai». Exemple :
- bool result = (2 < 3) || (1 == 2);
Cet exemple est "vrai", car son premier opérande est "vrai". Tout comme l'opérateur &&, le calcul est effectué rapidement : si le premier opérande est vrai, le deuxième n'est pas calculé du tout, car le résultat est déjà connu. On l'appelle aussi opérateur logique de court-circuit "ou".
Opérateurs logiques & et |
Les opérateurs de comparaison & et | sont respectivement similaires à && et ||. La différence réside dans le fait que les deux opérandes sont calculés l'un après l'autre, bien que le résultat final soit connu à l'avance. C'est pourquoi ces opérateurs de comparaison sont également appelés opérateurs logiques de circuit complet et sont très rarement utilisés.
Par exemple, lorsque deux opérandes sont comparés avec & et que le premier est évalué "faux", le calcul du deuxième opérande est quand même exécuté. Le résultat est clairement "faux". De même, lorsque deux opérandes sont comparés avec | et que le premier est "vrai", on évalue quand même le deuxième opérande et le résultat final est néanmoins "vrai".
Il ne faut pas confondre les opérateurs booléens & et | avec les opérateurs bit à bit & et |. Bien qu'ils soient écrits de la même manière, ils prennent des paramètres différents (expressions booléennes ou entières) et renvoient des résultats différents (booléens ou entiers) et leurs actions ne sont pas identiques.
Opérateurs logiques ^ et !
L'opérateur ^, également connu sous le nom de OU exclusif (XOR), fait partie des opérateurs à circuit complet, car les deux opérandes sont calculés l'un après l'autre. Le résultat de l'application de l'opérateur est vrai si exactement l'un des opérandes est vrai, mais pas les deux simultanément. Sinon, le résultat est faux. Voici un exemple :
Le résultat est le suivant :
Exclusif OU: FalseL'expression précédente est évaluée comme fausse, car les deux opérandes : (2 <3) et (4 > 3) sont vrais.
L'opérateur ! renvoie la valeur inversée de l'expression booléenne à laquelle il est attaché. Exemple :
L'expression ci-dessus peut être lue comme "l'opposé de la vérité de la phrase "7 == 5". Le résultat de ce modèle est True (l'opposé de False). Notez que lorsque nous affichons la valeur true, elle s'affiche sur la console sous la forme "True" (avec une majuscule). Ce "défaut" provient du langage VB.NET fonctionnant également dans .NET Framework.
Instructions conditionnelles "if" et "if-else"
Après avoir vu comment comparer des expressions, nous allons continuer avec les instructions conditionnelles, nous permettant d'implémenter la logique de programmation.
Les instructions conditionnelles if et if-else sont des instructions de contrôle conditionnelles. Grâce à elles, le programme peut se comporter différemment en fonction d'une condition définie vérifiée lors de l'exécution de l'instruction.
Instruction conditionnelle "if"
Le format principal des instructions conditionnelles si est le suivant :
if (expression booléenne) { Corps de l'instruction conditionnelle ; } |
Il comprend : la clause if, l'expression booléenne et le corps de l'instruction conditionnelle.
L'expression booléenne peut être une variable booléenne ou une expression logique booléenne. Les expressions booléennes ne peuvent pas être des entiers (contrairement à d'autres langages de programmation comme C et C++).
Le corps de l'instruction est la partie enfermée entre les accolades : {}. Elle peut être constituée d'une ou plusieurs opérations (instructions). Lorsqu'il y a plusieurs opérations, on a un opérateur de bloc complexe, c'est-à-dire une suite de commandes qui se succèdent, enfermées dans des accolades.
L'expression entre les accolades suivant le mot clef if doit renvoyer la valeur booléenne true ou false. Si l'expression est calculée à la valeur true, alors le corps d'une instruction conditionnelle est exécuté. Si le résultat est false, alors les opérateurs du corps seront ignorés.
Prenons un exemple d'utilisation d'une instruction conditionnelle if :
- static void Main() {
- Console.WriteLine("Entrez deux chiffres.");
- Console.Write("Entrez le premier numéro :");
- int premierNombre = int.Parse(Console.ReadLine());
- Console.Write("Entrez le deuxième numéro :");
- int deuxiemeNombre = int.Parse(Console.ReadLine());
- int grandNombre = premierNombre;
- if (deuxiemeNombre > premierNombre) {
- grandNombre = deuxiemeNombre;
- }
- Console.WriteLine("Le plus grand nombre est : {0}", grandNombre);
- }
Si nous commençons l'exemple et saisissons les nombres 4 et 5, nous obtiendrons le résultat suivant :
Entrez deux chiffres.Entrez le premier numéro : 3
Entrez le deuxième numéro : 7
Le plus grand nombre est : 7
Instruction conditionnelle "if" et accolades
Si nous n'avons qu'un seul opérateur dans le corps de l'instruction if, les accolades désignant le corps de l'opérateur conditionnel peuvent être omises, comme indiqué ci-dessous. Cependant, il est recommandé de les utiliser même si nous n'avons qu'un seul opérateur. Cela rendra le code plus lisible.
Voici un exemple d'omission des accolades pouvant prêter à confusion :
Dans cet exemple, le code est formaté de manière trompeuse et donne l'impression que les deux instructions d'affichage font partie du corps du bloc if. En fait, cela n'est vrai que pour la première.
Placez toujours des accolades { } pour le corps des blocs «if» même s'ils ne sont constitués que d'un seul opérateur !
Instruction conditionnelle "if-else"
En C#, comme dans la plupart des langages de programmation, il existe une instruction conditionnelle avec clause else : l'instruction if-else. Son format est le suivant :
if (expression booléenne) { Corps de l'instruction conditionnelle ; } else { Corps de l'instruction else ; } |
Le format de la structure if-else se compose du mot réservé if, de l'expression booléenne, du Corps de l'instruction conditionnelle, du mot réservé else et de Corps de l'instruction else. Le corps de la structure else peut être constitué d'un ou plusieurs opérateurs, placés entre accolades, comme le corps d'une instruction conditionnelle.
Cette instruction fonctionne comme suit : l'expression entre parenthèses (une expression booléenne) est calculée. Le résultat du calcul doit être booléen - vrai ou faux. Selon le résultat, il y a deux résultats possibles. Si l'expression booléenne est calculée à true, le corps de l'instruction conditionnelle est exécuté et l'instruction else est omise et ses opérateurs ne s'exécutent pas. Sinon, si l'expression booléenne est calculée à false, le corps else est exécuté, le corps principal de l'instruction conditionnelle est omis et les opérateurs qu'elle contient ne sont pas exécutés.
Exemple d'instruction conditionnelle "if-else"
Examinons l'exemple suivant et illustrons le fonctionnement de l'instruction if-else :
Le code du programme peut être interprété comme suit : si x>7, le résultat à la fin est : "x est supérieur à 7", sinon (else) le résultat est : "x n'est pas supérieur à 7". Dans ce cas, puisque x=3, après le calcul de l'expression booléenne l'opérateur de la structure else sera exécuté. Le résultat de l'exemple est :
x n'est pas supérieur à 7Instructions "if" imbriquées
Parfois, la logique de programmation d'un programme ou d'une application doit être représentée par plusieurs structures if contenues les unes dans les autres. Nous les appelons structures if imbriquées ou structures if-else imbriquées.
Nous appelons imbrication le placement d'une structure if ou if-else dans le corps d'une autre structure if ou else. Dans de telles situations, chaque clause else correspond à la clause if précédente la plus proche. C'est ainsi que nous comprenons quelle clause else se rapporte à quelle clause if.
Il n'est pas recommandé de dépasser trois niveaux imbriqués, c'est-à-dire que nous ne devons pas imbriquer plus de trois instructions conditionnelles les unes dans les autres. Si pour une raison quelconque nous devons imbriquer plus de trois structures, nous devons exporter une partie du code dans une méthode séparée.
Voici un exemple d'utilisation de structures if imbriquées :
Dans l'exemple ci-dessus, nous avons deux nombres et nous les comparons en deux étapes : nous comparons d'abord s'ils sont égaux et si ce n'est pas le cas, nous comparons à nouveau pour déterminer lequel est le plus grand. Voici le résultat de l'exécution du code ci-dessus :
Le premier nombre est plus grand.Séquences de «if-else-if-else-...»
Parfois, nous devons utiliser une séquence de structures if, où la clause else est une nouvelle structure if. Si nous utilisons des structures if imbriquées, le code serait poussé trop loin vers la droite. C'est pourquoi dans de telles situations, il est permis d'utiliser un nouveau if juste après le else. C'est même considéré comme une bonne pratique. Voici un exemple :
- char ch = 'X';
- if (ch == 'A' || ch == 'a') {
- Console.WriteLine("Voyelle [a]");
- } else if (ch == 'E' || ch == 'e') {
- Console.WriteLine("Voyelle [e]");
- } else if (ch == 'I' || ch == 'i') {
- Console.WriteLine("Voyelle [i]");
- } else if (ch == 'O' || ch == 'o') {
- Console.WriteLine("Voyelle [o]");
- } else if (ch == 'U' || ch == 'u') {
- Console.WriteLine("Voyelle [u]");
- } else if (ch == 'Y' || ch == 'y') {
- Console.WriteLine("Voyelle [i grec]");
- } else {
- Console.WriteLine("Consonne");
- }
Le programme de l'exemple effectue une série de comparaisons d'une variable pour vérifier si elle fait partie des voyelles de l'alphabet français. Chaque comparaison suivante n'est effectuée que dans le cas où la comparaison précédente n'était pas vraie. Au final, si aucune des conditions if n'est remplie, la dernière clause else est exécutée. Ainsi, le résultat de l'exemple est le suivant :
ConsonneInstructions conditionnelles «if» - Bonnes pratiques
Voici quelques lignes directrices recommandez pour la rédaction de structures if :
- Utilisez des blocs, entourés d'accolades {} après if et else afin d'éviter toute ambiguïté.
- Formatez toujours correctement le code en le décalant d'une tabulation vers l'intérieur après if et else, pour plus de lisibilité et éviter toute ambiguïté.
- Préférez la structure switch-case à une série de structures if-else-if-else-... ou à une instruction if-else imbriquée, si possible. Nous aborderons la construction switch-case dans la section suivante.
Instruction conditionnelle «switch-case»
Dans la section suivante, nous aborderons l'instruction conditionnelle switch. Elle permet de choisir parmi une liste de possibilités.
Comment fonctionne l'instruction «switch-case» ?
La structure switch-case choisit quelle partie du code de programmation exécuter en fonction de la valeur calculée d'une certaine expression (le plus souvent de type entier). Le format de la structure pour choisir une option est le suivant :
switch (sélecteur_entier) { case valeur_entiere_1: instructions; break; case valeur_entiere_2: instructions; break; // ... default: instructions; break; } |
Le sélecteur est une expression renvoyant une valeur résultante pouvant être comparée, comme un nombre ou une chaîne de caractères. L'opérateur switch compare le résultat du sélecteur à chaque valeur répertoriée dans les étiquettes case dans le corps de la structure switch. Si une correspondance est trouvée dans une étiquette case, la structure correspondante est exécutée (simple ou complexe). Si aucune correspondance n'est trouvée, l'instruction default est exécutée (lorsqu'elle existe). La valeur du sélecteur doit être calculée avant de la comparer aux valeurs à l'intérieur de la structure switch. Les étiquettes ne doivent pas avoir de valeurs répétitives, elles doivent être uniques.
Comme on peut le voir à partir de la définition ci-dessus, chaque case se termine par l'opérateur break, terminant le corps de la structure switch. Le compilateur C# requiert le mot break à la fin de chaque section case contenant du code. Si aucun code n'est trouvé après une instruction case, le break peut être omis et l'exécution passe à l'instruction case suivante et continue jusqu'à ce qu'elle trouve un opérateur break. Après la structure par défaut, le break est obligatoire.
Il n'est pas nécessaire que la clause par défaut soit la dernière, mais il est recommandé de la placer à la fin, et non au milieu de la structure du commutateur.
Règles pour les expressions dans switch
L'instruction switch est un moyen clair d'implémenter une sélection parmi de nombreuses options (à savoir, un choix parmi quelques manières alternatives d'exécuter le code). Elle nécessite un sélecteur, qui est calculé sur une certaine valeur. Le type de sélecteur peut être un nombre entier, un caractère, une chaîne ou une énumération. Si nous voulons utiliser par exemple un tableau ou un float comme sélecteur, cela ne fonctionnera pas. Pour les types de données non entiers, nous devons utiliser une série d'instructions if.
Utilisation de plusieurs étiquettes
L'utilisation de plusieurs étiquettes est appropriée lorsque nous souhaitons exécuter la même structure dans plusieurs cas. Regardons l'exemple suivant :
Dans l'exemple ci-dessus, nous implémentons plusieurs étiquettes en utilisant des instructions case sans pause après elles. Dans ce cas, la valeur entière du sélecteur est d'abord calculée, c'est-à-dire 8, puis cette valeur est comparée à chaque valeur entière dans les instructions case. Lorsqu'une correspondance est trouvée, le bloc de code la suivant est exécuté. Si aucune correspondance n'est trouvée, le bloc par défaut est exécuté. Le résultat de l'exemple ci-dessus est le suivant :
Le nombre n'est pas premier !Bonnes pratiques lors de l'utilisation de "switch-case"
- Une bonne pratique lors de l'utilisation de l'instruction switch est de placer l'instruction default à la fin, afin d'avoir un code plus facile à lire.
- Il est bon de placer en premier les cas, gérant les situations les plus courantes. Les instructions case, gérant les situations se produisant rarement, peuvent être placées à la fin de la structure.
- Si les valeurs dans les étiquettes case sont des entiers, il est recommandé de les classer par ordre croissant.
- Si les valeurs dans les étiquettes case sont de type caractère, il est recommandé de trier les étiquettes case par ordre alphabétique.
- Il est conseillé de toujours utiliser un bloc default pour gérer les situations ne pouvant pas être traitées dans le fonctionnement normal du programme. Si dans le fonctionnement normal du programme le bloc default n'est pas accessible, vous pouvez y placer un code signalant une erreur.