Les opérateurs et les expressions
En C#, les opérateurs et les expressions sont essentiels pour manipuler les données et structurer la logique des programmes. Les opérateurs permettent de réaliser des opérations spécifiques sur des valeurs (appelées opérandes). Ils se divisent en plusieurs catégories, comme les opérateurs arithmétiques (+, -, *, /, %), les opérateurs de comparaison (==, <, >), ou encore les opérateurs logiques (&&, ||, !). D'autres incluent les opérateurs conditionnels (? :, ??), les opérateurs bit à bit (&, |, ^), et les opérateurs liés aux types (is, as, typeof). Ces outils sont indispensables pour effectuer des calculs, des vérifications ou des manipulations complexes dans le code.
Une expression en C# est une combinaison d'opérateurs, d'opérandes, de constantes et de variables, produisant une valeur. Par exemple, une expression arithmétique comme a + b * c retourne un résultat numérique, tandis qu'une expression booléenne comme (x > 10) && (y != 5) évalue une condition logique. Les expressions conditionnelles, telles que age >= 18 ? "Adulte" : "Mineur", permettent d'obtenir des valeurs en fonction d'une condition. Les expressions jouent un rôle clef dans les décisions et calculs dynamiques.
Dans cette page, nous allons nous familiariser avec les opérateurs de C# et les actions qu'ils peuvent effectuer lorsqu'ils sont utilisés avec les différents types de données. Au début, nous expliquerons quels opérateurs ont la priorité la plus élevée et nous analyserons les différents types d'opérateurs, en fonction du nombre de paramètres qu'ils peuvent prendre et des actions qu'ils effectuent. Dans la deuxième partie, nous examinerons la conversion des types de données. Nous expliquerons quand et pourquoi cela est nécessaire et comment travailler avec différents types de données. À la fin du chapitre, nous accorderons une attention particulière aux expressions et à la manière dont nous devons les utiliser. Enfin, nous avons préparé des exercices pour renforcer nos connaissances sur le contenu de cette page.
Les opérateurs
Chaque langage de programmation utilise des opérateurs, grâce auxquels nous pouvons effectuer différentes actions sur les données. Examinons les opérateurs en C# et voyons à quoi ils servent et comment ils sont utilisés.
C'est quoi un opérateur ?
Après avoir appris comment déclarer et définir une variable dans la page Les types primitifs et variables, nous verrons comment effectuer diverses opérations avec eux. Pour cela, nous nous familiariserons avec les opérateurs.
Les opérateurs permettent de traiter des types de données et des objets primitifs. Ils prennent en entrée un ou plusieurs opérandes et renvoient une valeur en résultat. Les opérateurs en C# sont des caractères spéciaux (tels que "+", ".", "^",...) et ils effectuent des transformations sur un, deux ou trois opérandes. Des exemples d'opérateurs en C# sont les signes d'addition, de soustraction, de multiplication et de division des nombres mathématiques (+, -, *, /) et les opérations qu'ils effectuent sur les entiers et les nombres réels.
Les opérateurs en C#
Les opérateurs en C# peuvent être séparés en plusieurs catégories différentes :
- Opérateurs arithmétiques : Ils sont utilisés pour effectuer des opérations mathématiques simples.
- Opérateurs d'affectation : ils permettent d'affecter des valeurs à des variables.
- Opérateurs de comparaison : Ils permettent de comparer deux littéraux et/ou variables.
- Opérateurs logiques : Opérateurs fonctionnant avec des types de données booléens et des expressions booléennes.
- Opérateurs binaires : Ils permettent d'effectuer des opérations sur la représentation binaire de données numériques.
- Opérateurs de conversion de type : Ils permettent de convertir des données d'un type à un autre.
Catégories d'opérateurs
Vous trouverez ci-dessous une liste des opérateurs, répartis en catégories :
Catégorie | Opérateur |
---|---|
Arithmétique | -, +, *, /, %, ++, -- |
Logique | &&, ||, !, ^ |
Binaire | &, |, ^, ~, <<, >> |
Comparaison | ==,!=, >, <, >=, <= |
Affectation | =, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>= |
Concaténation de chaînes de caractères | + |
Conversion de type | (type), as, is, typeof, sizeof |
Autre | ., new, (), [], ?:, ?? |
Types d'opérateurs par nombre de paramètres
Les opérateurs peuvent être séparés en différents types en fonction du nombre de paramètres qu'ils peuvent prendre :
Type d'opérateur | Nombre de paramètres (opérandes) |
---|---|
Unaire | Prend un opérande |
Binaire | Prend deux opérandes |
Ternaire | Prend trois opérandes |
Tous les opérateurs binaires en C# sont associatifs à gauche, c'est-à-dire que les expressions sont calculées de gauche à droite, à l'exception des opérateurs d'affectation. Tous les opérateurs d'affectation et les opérateurs conditionnels ?: et ?? sont associatifs à droite, c'est-à-dire que les expressions sont calculées de droite à gauche. Les opérateurs unaires ne sont pas associatifs.
Certains opérateurs en C# effectuent différentes opérations sur les différents types de données. Par exemple l'opérateur +. Lorsqu'il est utilisé sur des types de données numériques (int, long, float,...), l'opérateur effectue une addition mathématique. Cependant, lorsque nous l'utilisons sur des chaînes de caractères, l'opérateur concatène (joint ensemble) le contenu des deux variables/littéraux et renvoie la nouvelle chaîne de caractères.
Voici un exemple d'utilisation des opérateurs :
L'exemple montre comment, comme expliqué ci-dessus, lorsque l'opérateur + est utilisé sur des nombres, il renvoie une valeur numérique, et lorsqu'il est utilisé sur des chaînes de caractères, il renvoie des chaînes de caractères concaténées.
Priorité des opérateurs en C#
Certains opérateurs ont la priorité sur d'autres. Par exemple, en mathématiques, la multiplication a la priorité sur l'addition. Les opérateurs ayant une priorité plus élevée sont calculés avant ceux ayant une priorité plus faible. L'opérateur () est utilisé pour modifier la priorité et comme en mathématiques, il est calculé en premier.
Le tableau suivant illustre la priorité des opérateurs en C# :
Priorité | Opérateurs |
---|---|
Priorité la plus élevée ... |
(, ) |
++, -- (comme postfixe), new, (type), typeof, sizeof | |
++, -- (comme préfixe), +, - (unaire), !, ~ | |
*, /, % | |
+ (concaténation de chaînes de caractères) | |
+, - | |
<<, >> | |
<, >, <=, >=, is, as | |
==, != | |
&, ^, | | |
Priorité la plus basse | && |
|| | |
?:, ?? | |
=, *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |= |
Les opérateurs situés en haut du tableau ont une priorité plus élevée que ceux situés en dessous d'eux, et ils ont donc un avantage dans le calcul d'une expression. Pour modifier la priorité d'un opérateur, nous pouvons utiliser des parenthèses.
Lorsque nous écrivons des expressions plus complexes ou comportant de nombreux opérateurs, il est recommandé d'utiliser des parenthèses pour éviter les difficultés de lecture et de compréhension du code. Par exemple :
- // Ambiguë
- x + y / 100
- // Sans ambiguïté, recommandé
- x + (y / 100)
Opérateurs arithmétiques
Les opérateurs arithmétiques en C# +, -, * sont les mêmes que ceux utilisés en mathématiques. Ils effectuent des additions, des soustractions et des multiplications sur des valeurs numériques et le résultat est également une valeur numérique.
L'opérateur de division / a un effet différent sur les nombres entiers et réels. Lorsque nous divisons un entier par un entier (comme int, long et sbyte), la valeur renvoyée est un entier (pas d'arrondi, la partie fractionnaire est coupée). Une telle division est appelée division entière. Exemple de division entière : 7 / 3 = 2.
La division entière par 0 n'est pas autorisée et provoque une exception d'exécution DivideByZeroException. Le reste de la division entière d'entiers peut être obtenu par l'opérateur %. Par exemple, 7 % 3 = 1 et -10 % 2 = 0.
Lors de la division de deux nombres réels ou de deux nombres dont l'un est réel (par exemple float, double,...), une division réelle est effectuée (pas un entier) et le résultat est un nombre réel avec une partie entière et une partie fractionnaire. Par exemple : 5,0 / 2 = 2,5. Dans la division de nombres réels, il est permis de diviser par 0,0 et le résultat est respectivement +∞ (infini), -∞ (-infini) ou NaN (valeur non valide).
L'opérateur d'augmentation de un (incrément) ++ ajoute une unité à la valeur de la variable, respectivement l'opérateur -- (décrément) soustrait une unité de la valeur. Lorsque nous utilisons les opérateurs ++ et -- comme préfixe (lorsque nous les plaçons immédiatement avant la variable), la nouvelle valeur est calculée en premier, puis le résultat est renvoyé. Lorsque nous utilisons les mêmes opérateurs que post-fixe (c'est-à-dire lorsque nous les plaçons immédiatement après la variable), la valeur d'origine de l'opérande est renvoyée en premier, puis l'addition ou la soustraction est effectuée.
Voici quelques exemples d'opérateurs arithmétiques et de leur effet :
- int carrePerimetre = 17;
- double carreCote = carrePerimetre / 4.0;
- double carreSuperficie = carreCote * carreCote;
- Console.WriteLine(carreCote); // 4.25
- Console.WriteLine(carreSuperficie); // 18.0625
- int a = 5;
- int b = 4;
- Console.WriteLine(a + b); // 9
- Console.WriteLine(a + (b++)); // 9
- Console.WriteLine(a + b); // 10
- Console.WriteLine(a + (++b)); // 11
- Console.WriteLine(a + b); // 11
- Console.WriteLine(14 / a); // 2
- Console.WriteLine(14 % a); // 4
- int un = 1;
- int zero = 0;
- // Console.WriteLine(un / zero); // DivideByZeroException
- double dMinusOne = -1.0;
- double dZero = 0.0;
- Console.WriteLine(dMinusOne / zero); // -Infinity
- Console.WriteLine(un / dZero); // Infinity
Les opérateurs logiques
Les opérateurs logiques (booléens) prennent des valeurs booléennes et renvoient un résultat booléen (true ou false). Les opérateurs booléens de base sont « AND » (&&), « OR » (||), « exclusif » (^) et la négation logique (!).
Le tableau suivant contient les opérateurs logiques en C# et les opérations qu'ils effectuent :
x | y | !x | x && y | x || y | x ^ y |
---|---|---|---|---|---|
true | true | false | true | true | false |
true | false | false | false | true | true |
false | true | true | false | true | true |
false | false | true | false | false | false |
Le tableau et l'exemple suivant montrent que le "AND" logique (&&) renvoie true uniquement lorsque les deux variables contiennent true. Le "OR" logique (||) renvoie true lorsqu'au moins un des opérandes est true. L'opérateur de négation logique (!) modifie la valeur du paramètre. Par exemple, si l'opérande a une valeur true et qu'un opérateur de négation est appliqué, la nouvelle valeur sera false. L'opérateur de négation est un opérateur unaire et il est placé avant le paramètre. Le "OR" exclusif (^) renvoie true si un seul des deux opérandes a la valeur true. Si les deux opérandes ont des valeurs différentes, le "OR" exclusif renvoie le résultat true, s'ils ont les mêmes valeurs, il renvoie false.
L'exemple suivant illustre l'utilisation des opérateurs logiques et leurs actions :
Lois de De Morgan
Les opérations logiques relèvent des lois de De Morgan issues de la logique mathématique :
- !(a && b) == (!a || !b)
- !(a || b) == (!a && !b)
La première loi stipule que la négation de la conjonction (ET logique) de deux propositions équivaut à la disjonction (OU logique) de leurs négations. La deuxième loi stipule que la négation de la disjonction des deux énoncés équivaut à la conjonction de leurs négations.
Opérateur de concaténation de chaînes de caractères
L'opérateur + est utilisé pour joindre des chaînes de caractères (string). Il concatène (joint) deux ou plusieurs chaînes de caractères et renvoie le résultat sous forme de nouvelle chaîne de caractères. Si au moins un des paramètres de l'expression est de type string, et qu'il existe d'autres opérandes de type différent de string, ils seront automatiquement convertis en type string, ce qui permet une concaténation réussie des chaînes de caractères.
C'est intéressant de voir comment le runtime .NET gère à la volée de telles incompatibilités d'opérations, ce qui nous fait gagner du temps de codage et nous permet de nous concentrer sur les objectifs principaux de notre tâche de programmation ! Cependant, il est recommandé de ne pas oublier de convertir les variables sur lesquelles nous souhaitons appliquer une opération; nous devons plutôt les convertir au type approprié pour chaque opération, afin d'avoir un contrôle total du résultat final et d'éviter les conversions de type implicites.
Voici un exemple montrant des concaténations de deux chaînes de caractères et d'une chaîne de caractères avec un nombre :
Dans l'exemple, nous initialisons deux variables de type chaîne de caractères et leur attribuons des valeurs. Sur la troisième et la quatrième ligne, nous concaténons les deux chaînes et passons les résultats à la méthode Console.WriteLine() pour les afficher sur la console. Sur la ligne suivante, nous joignons la chaîne de caractères résultante avec un espace et le nombre 5. Nous attribuons la valeur renvoyée à la variable csharpDotNet5, étant automatiquement convertie en type chaîne de caractères. Sur la dernière ligne, nous affichons le résultat.
La concaténation (assemblage, collage) de chaînes de caractères est une opération lente et doit être utilisée avec précaution. Il est recommandé d'utiliser la classe StringBuilder pour les opérations itératives (répétitives) sur les chaînes de caractères.
Opérateurs au niveau du bit
Un opérateur au niveau du bit est un opérateur agissant sur la représentation binaire des types numériques. Dans les ordinateurs, toutes les données, et en particulier les données numériques, sont représentées par une série de uns et de zéros. Le système de numération binaire est utilisé à cette fin. Par exemple, le nombre 55 dans le système de numération binaire est représenté par 00110111.
La représentation binaire des données est pratique car le zéro et le un en électronique peuvent être mis en ouvre par des circuits booléens, dans lesquels le zéro est représenté par «pas d'électricité» ou par exemple avec une tension de -5 V et le un est présenté par «avoir de l'électricité» ou par exemple avec une tension de +5 V.
Pour l'instant nous pouvons considérer que les nombres dans les ordinateurs sont représentés par des uns et des zéros, et que les opérateurs au niveau du bit sont utilisés pour analyser et changer ces uns en zéros et vice versa.
Les opérateurs au niveau du bit sont très similaires aux opérateurs logiques. En fait, nous pouvons imaginer que les opérateurs logiques et au niveau du bit effectuent la même chose mais en utilisant des types de données différents. Les opérateurs logiques fonctionnent avec les valeurs true et false (valeurs booléennes), tandis que les opérateurs au niveau du bit fonctionnent avec des valeurs numériques et sont appliqués au niveau du bit sur leur représentation binaire, c'est-à-dire qu'ils fonctionnent avec les bits du nombre (les chiffres 0 et 1 qui le composent). Tout comme les opérateurs logiques en C#, il existe des opérateurs au niveau du bit «AND» (&), «OR» au niveau du bit (|), la négation au niveau du bit (~) et l'exclusion de « OR » (^).
Opérateurs au niveau du bit et leurs performances
Les performances des opérateurs au niveau du bit sur les chiffres binaires 0 et 1 sont présentées dans le tableau suivant :
x | y | ~x | x & y | x | y | x ^ y |
---|---|---|---|---|---|
1 | 1 | 0 | 1 | 1 | 0 |
1 | 0 | 0 | 0 | 1 | 1 |
0 | 1 | 1 | 0 | 1 | 1 |
0 | 0 | 1 | 0 | 0 | 0 |
Comme nous le voyons, les opérateurs binaires et logiques sont très similaires. La différence dans l'écriture de "AND" et "OR" est que les opérateurs logiques s'écrivent avec une double esperluette (&&) et une double barre verticale (||), et les opérateurs binaires avec une seule esperluette ou une barre verticale (& et |). Les opérateurs binaires et logiques pour "OR" exclusif sont les mêmes "^". Pour la négation logique, nous utilisons "!", tandis que pour la négation binaire (inversion), l'opérateur "~" est utilisé.
En programmation, il existe deux opérateurs binaires n'ayant pas d'analogue dans les opérateurs logiques. Il s'agit du décalage de bits à gauche (<<) et du décalage de bits à droite (>>). Utilisés sur des valeurs numériques, ils déplacent tous les bits de la valeur vers la gauche ou la droite. Les bits se trouvant en dehors du nombre sont perdus et remplacés par 0.
Les opérateurs de décalage de bits s'utilisent de la manière suivante : sur le côté gauche de l'opérateur, nous plaçons la variable (opérande) avec laquelle nous voulons utiliser l'opérateur, sur le côté droit, nous mettons une valeur numérique, indiquant de combien de bits nous voulons décaler. Par exemple, 3 << 2 signifie que nous voulons déplacer les bits du nombre trois, deux fois vers la gauche. Le nombre 3 présenté en bits ressemble à ceci : "0000 0011". Lorsque vous vous déplacez deux fois vers la gauche, la valeur binaire ressemblera à ceci : "0000 1100", et cette séquence de bits est le nombre 12. Si nous regardons l'exemple, nous pouvons voir qu'en fait nous avons multiplié le nombre par 4. Le décalage de bits lui-même peut être représenté comme une multiplication (décalage de bits vers la gauche) ou une division (décalage de bits vers la droite) par une puissance de 2. Ce phénomène est dû à la nature du système de numération binaire. Un exemple de déplacement vers la droite est 6 >> 2, ce qui signifie déplacer le nombre binaire « 0000 0110 » de deux positions vers la droite. Cela signifie que nous perdrons deux chiffres les plus à droite et les alimenterons avec des zéros à gauche. Le résultat final sera « 0000 0001 », soit 1.
Voici un exemple d'utilisation d'opérateurs binaires. La représentation binaire des nombres et les résultats des opérateurs binaires sont indiqués dans les commentaires (texte jaune) :
- byte a = 3; // 0000 0011 = 3
- byte b = 5; // 0000 0101 = 5
- Console.WriteLine(a | b); // 0000 0111 = 7
- Console.WriteLine(a & b); // 0000 0001 = 1
- Console.WriteLine(a ^ b); // 0000 0110 = 6
- Console.WriteLine(~a & b); // 0000 0100 = 4
- Console.WriteLine(a << 1); // 0000 0110 = 6
- Console.WriteLine(a << 2); // 0000 1100 = 12
- Console.WriteLine(a >> 1); // 0000 0001 = 1
Dans l'exemple, nous créons et initialisons d'abord les valeurs de deux variables a et b. Ensuite, nous imprimons sur la console les résultats de certaines opérations au niveau du bit sur les deux variables. La première opération que nous appliquons est "OR". L'exemple montre que pour toutes les positions où il y avait 1 dans la représentation binaire des variables a et b, il y a aussi 1 dans le résultat. La deuxième opération est "AND". Le résultat de l'opération ne contient 1 que dans le bit le plus à droite, car le seul endroit où a et b ont 1 en même temps est leur bit le plus à droite. "OU" exclusif ne renvoie des 1 que dans les positions où a et b ont des valeurs différentes dans leurs bits binaires. Enfin, la négation logique et le décalage au niveau du bit : gauche et droite, sont illustrés.
Opérateurs de comparaison
Les opérateurs de comparaison en C# sont utilisés pour comparer deux ou plusieurs opérandes. C# prend en charge les opérateurs de comparaison suivants :
- supérieur à (>)
- inférieur à (<)
- supérieur ou égal à (>=)
- inférieur ou égal à (<=)
- égalité (==)
- différence (!=)
Tous les opérateurs de comparaison en C# sont binaires (prennent deux opérandes) et le résultat renvoyé est une valeur booléenne (true ou false). Les opérateurs de comparaison ont une priorité inférieure à celle des opérateurs arithmétiques mais supérieure à celle des opérateurs d'affectation.
L'exemple suivant illustre l'utilisation des opérateurs de comparaison en C# :
- int x = 16, y = 7;
- Console.WriteLine("x > y : " + (x > y)); // True
- Console.WriteLine("x < y : " + (x < y)); // False
- Console.WriteLine("x >= y : " + (x >= y)); // True
- Console.WriteLine("x <= y : " + (x <= y)); // False
- Console.WriteLine("x == y : " + (x == y)); // False
- Console.WriteLine("x != y : " + (x != y)); // True
Dans l'exemple, nous créons d'abord deux variables x et y et nous leur attribuons les valeurs 16 et 7. Sur la ligne suivante, nous affichons sur la console à l'aide de la méthode Console.WriteLine(...) le résultat de la comparaison des deux variables x et y à l'aide de l'opérateur >. La valeur renvoyée est true car x a une valeur supérieure à y. De même, dans les lignes suivantes, les résultats des 5 autres opérateurs de comparaison, utilisés pour comparer les variables x et y, sont affichés.
Opérateurs d'affectation
L'opérateur permettant d'affecter une valeur à une variable est «=» (le caractère de l'équation mathématique). La syntaxe utilisée pour affecter une valeur est la suivante :
operande1 = litteral, expression ou operande2; |
Voici un exemple pour montrer l'utilisation de l'opérateur d'affectation :
Dans l'exemple, nous attribuons la valeur 7 à la variable x. Sur la deuxième ligne, nous attribuons un texte littéral à la variable chaineBonjour, et sur la troisième ligne, nous copions la valeur de la variable x dans la variable y.
Affectation en cascade
L'opérateur d'affectation peut être utilisé en cascade (plusieurs fois dans la même expression). Dans ce cas, les affectations sont effectuées consécutivement de droite à gauche. Voici un exemple :
- int x, y, z;
- x = y = z = 74;
Sur la première ligne de l'exemple, nous initialisons trois variables et sur la deuxième ligne, nous leur attribuons la valeur 74.
L'opérateur d'affectation en C# est "=", tandis que l'opérateur de comparaison est "==". L'échange des deux opérateurs est une erreur courante lorsque nous écrivons du code. Veillez à ne pas confondre l'opérateur de comparaison et l'opérateur d'affectation, car ils se ressemblent beaucoup.
Opérateurs d'affectation composés
Outre l'opérateur d'affectation, il existe également des opérateurs d'affectation composés. Ils permettent de réduire le volume du code en saisissant deux opérations ensemble avec un opérateur : opération et affectation. Les opérateurs composés ont la syntaxe suivante :
operande1 operateur= operande2; |
L'expression supérieure est la suivante :
operande1 = operande1 operateur operande2; |
Voici un exemple d'opérateur composé pour l'affectation :
Les opérateurs d'affectation composés les plus couramment utilisés sont += (ajoute la valeur de l'opérande 2 à l'opérande 1), -= (soustrait la valeur de l'opérande de droite de la valeur de l'opérande de gauche). Les autres opérateurs d'affectation composés sont *=, /= et %=.
L'exemple suivant donne une bonne idée du fonctionnement des opérateurs d'affectation composés :
Dans l'exemple, nous créons d'abord les variables x et y et leur attribuons les valeurs 7 et 4. Sur la ligne suivante, nous affichons sur la console y, après lui avoir attribué une nouvelle valeur en utilisant l'opérateur *= et le littéral 2. Le résultat de l'opération est 8. Plus loin dans l'exemple, nous appliquons les autres opérateurs d'affectation composés et imprimons le résultat sur la console.
Opérateur conditionnel ?:
L'opérateur conditionnel ?: utilise la valeur booléenne d'une expression pour déterminer laquelle des deux autres expressions doit être calculée et renvoyée comme résultat. L'opérateur fonctionne sur trois opérandes et c'est pourquoi il est appelé opérateur ternaire. Le caractère "?" est placé entre le premier et le deuxième opérande, et ":" est placé entre le deuxième et le troisième opérande. Le premier opérande (ou expression) doit être booléen et les deux opérandes suivants doivent être du même type, comme des nombres ou des chaînes de caractères.
L'opérateur ?: a la syntaxe suivante :
operande1 ? operande2 : operande3 |
Cela fonctionne comme ceci : si l'operande1 est défini sur true, l'opérateur renvoie comme résultat l'operande2. Sinon (si l'operande1 est défini sur false), l'opérateur renvoie comme résultat l'operande3.
Pendant l'exécution, la valeur du premier paramètre est calculée. Si elle a la valeur true, alors le deuxième paramètre (du milieu) est calculé et il est renvoyé comme résultat. Cependant, si le résultat calculé du premier paramètre est false, alors le troisième (dernier) paramètre est calculé et il est renvoyé comme résultat.
L'exemple suivant montre l'utilisation de l'opérateur «?:» :
Autres opérateurs
Jusqu'à présent, nous avons examiné les opérateurs arithmétiques, logiques et binaires, l'opérateur de concaténation de chaînes de caractères, ainsi que l'opérateur conditionnel ?:. Outre eux, en C#, il existe plusieurs autres opérateurs qui méritent d'être mentionnés.
L'opérateur "."
L'opérateur d'accès "." (point) permet d'accéder aux champs membres ou aux méthodes d'une classe ou d'un objet. Exemple d'utilisation de l'opérateur point :
Opérateur crochets []
Les crochets [] sont utilisés pour accéder aux éléments d'un tableau par index, ils sont appelés indexeurs. Les indexeurs sont également utilisés pour accéder aux caractères d'une chaîne de caractères. Exemple :
Opérateur de parenthèses ( )
Les parenthèses ( ) sont utilisés pour remplacer la priorité d'exécution des expressions et des opérateurs. Nous avons déjà vu comment fonctionnent les crochets.
Opérateur de conversion de type
L'opérateur de conversion de type (type) permet de convertir une variable d'un type à un autre.
Opérateur "as"
L'opérateur as est également utilisé pour la conversion de type, mais une conversion non valide renvoie null, ce qui n'est pas une exception.
Opérateur "new"
L'opérateur new est utilisé pour créer et initialiser de nouveaux objets.
Opérateur "is"
L'opérateur is permet de vérifier si un objet est compatible avec un type donné (vérifier le type de l'objet).
L'opérateur "??"
L'opérateur ?? est similaire à l'opérateur conditionnel ?:. La différence est qu'il est placé entre deux opérandes et renvoie l'opérande de gauche uniquement si sa valeur n'est pas nulle, sinon il renvoie l'opérande de droite. Exemple :
Voici un exemple montrant les opérateurs que nous venons d'expliquer :
- int a = 7;
- int b = 3;
- Console.WriteLine(a + b / 2); // 8
- Console.WriteLine((a + b) / 2); // 5
- string s = "Tomate";
- Console.WriteLine(s is string); // True
- string notNullString = s;
- string nullString = null;
- Console.WriteLine(nullString ?? "Indéterminée"); // Indéterminée
- Console.WriteLine(notNullString ?? "Déterminé"); // Tomate
Conversion et conversion de type
En général, les opérateurs fonctionnent sur des paramètres ayant le même type de données. Cependant, C# dispose d'une grande variété de types de données parmi lesquels nous pouvons choisir le plus approprié à un objectif particulier. Pour effectuer une opération sur des variables de deux types de données différents, nous devons les convertir toutes les deux dans le même type de données. La conversion de type (transfert de type) peut être explicite et implicite.
Toutes les expressions en C# ont un type. Ce type peut dériver de la structure de l'expression et des types, variables et littéraux utilisés dans celle-ci. Il est possible d'écrire une expression dont le type est inapproprié pour le contexte actuel. Dans certains cas, cela entraînera une erreur de compilation, mais dans d'autres cas, le contexte peut obtenir un type similaire ou lié au type de l'expression. Dans ce cas, le programme effectue une conversion de type cachée.
La conversion spécifique du type S vers le type T permet de traiter l'expression de type S comme une expression de type T lors de l'exécution du programme. Dans certains cas cela nécessitera une validation de la transformation. Voici quelques exemples :
- La conversion du type object vers le type string nécessitera une vérification à l'exécution pour s'assurer que la valeur est bien une instance de type string.
- La conversion du type string vers objet ne nécessite aucune vérification. Le type chaîne est un héritier du type objet et peut être converti vers sa classe de base sans risque d'erreur ou de perte de données.
- La conversion du type int vers long peut être effectuée sans vérification lors de l'exécution, car il n'y a aucun risque de perte de données puisque l'ensemble des valeurs de type int est un sous-ensemble de valeurs de type long.
- La conversion du type double vers long nécessite la conversion de la valeur à virgule flottante 64 bits en entier 64 bits. Selon la valeur, une perte de données est possible et il est donc nécessaire de convertir les types explicitement.
En C#, tous les types ne peuvent pas être convertis en tous les autres types, mais seulement en certains d'entre eux. Pour plus de commodité, nous allons regrouper certaines des transformations possibles en C# en fonction de leur type en trois catégories :
- conversion implicite ;
- conversion explicite ;
- conversion vers ou depuis une string;
Conversion de type implicite
La conversion de type implicite (masquée) n'est possible que lorsqu'il n'y a aucun risque de perte de données pendant la conversion, c'est-à-dire lors de la conversion d'un type d'intervalle inférieure vers un type d'intervalle supérieure (par exemple de int vers long). Pour effectuer une conversion implicite, il n'est pas nécessaire d'utiliser un opérateur et une telle transformation est donc dite implicite. La conversion implicite est effectuée automatiquement par le compilateur lorsque vous attribuez une valeur d'intervalle inférieure à une variable d'intervalle supérieure ou si l'expression a plusieurs types d'intervalles différentes. Dans ce cas, la conversion est exécutée dans le type d'intervalle la plus élevée.
Voici un exemple de conversion de type implicite :
Dans l'exemple, nous créons une variable myInt de type int et lui attribuons la valeur 7. Ensuite, nous créons une variable myLong de type long et lui attribuons la valeur contenue dans myInt. La valeur entreposée dans myLong est automatiquement convertie du type int au type long. Enfin, nous affichons le résultat de l'addition des deux variables. Comme les variables sont de types différents, elles sont automatiquement converties au type ayant la plus grande portée, c'est-à-dire au type long et le résultat étant affiché sur la console est à nouveau long. En effet, le paramètre donné à la méthode Console.WriteLine() est de type long, mais à l'intérieur de la méthode il sera à nouveau converti, cette fois en type string, afin de pouvoir être affiché sur la console. Cette transformation est effectuée par la méthode Long.ToString().
Conversions implicites possibles
Voici quelques conversions implicites possibles de types de données primitifs en C# :
- sbyte→ short, int, long, float, double, decimal;
- byte→ short, ushort, int, uint, long, ulong, float, double, decimal;
- short→ int, long, float, double, decimal;
- ushort→ int, uint, long, ulong, float, double, decimal;
- char → ushort, int, uint, long, ulong, float, double, decimal (bien que char soit un type de caractère, dans certains cas, il peut être considéré comme un nombre et avoir un comportement de type numérique, il peut même participer à des expressions numériques);
- uint → long, ulong, float, double, decimal;
- int → long, float, double, decimal;
- long → float, double, decimal;
- ulong → float, double, decimal;
- float → double.
Il n'y a aucune perte de données lors de la conversion de types de l'intervalle plus petite en types d'intervalle plus grande. La valeur numérique reste la même après la conversion. Il existe quelques exceptions. Lorsque vous convertissez le type int en type float (valeurs 32 bits), la différence est que int utilise tous les bits pour un nombre entier, tandis que float a une partie de bits utilisée pour la représentation d'une partie fractionnaire. Par conséquent, une perte de précision est possible en raison de l'arrondi lors de la conversion de int en float. Il en va de même pour la conversion de long 64 bits en double 64 bits.
Conversion de type explicite
La conversion de type explicite est utilisée chaque fois qu'il existe un risque de perte de données. Lors de la conversion d'un type à virgule flottante en type entier, il y a toujours une perte de données provenant de l'élimination de la partie fractionnaire et une conversion explicite est obligatoire (par exemple, double en long). Pour effectuer une telle conversion, il est nécessaire d'utiliser l'opérateur de conversion de données (type). Il peut également y avoir une perte de données lors de la conversion d'un type avec une plage plus large en un type avec un intervalle plus étroite (double en float ou long en int).
L'exemple suivant illustre l'utilisation de la conversion de type explicite et la perte de données pouvant se produire dans certains cas :
- double myDouble = 5.1d;
- Console.WriteLine(myDouble); // 5.1
- long myLong = (long)myDouble;
- Console.WriteLine(myLong); // 5
- myDouble = 5e9d; // 5 * 10^9
- Console.WriteLine(myDouble); // 5000000000
- int myInt = (int)myDouble;
- Console.WriteLine(myInt); // -2147483648
- Console.WriteLine(int.MinValue); // -2147483648
Dans la première ligne de l'exemple, nous attribuons une valeur 5.1 à la variable myDouble. Après avoir converti (explicitement) en type long en utilisant l'opérateur (long) et affiché sur la console la variable myLong, nous voyons que la variable a perdu sa partie fractionnaire, car long est un entier. Ensuite, nous attribuons à la variable réelle double précision myDouble la valeur 5 milliards. Enfin, nous convertissons myDouble en int par l'opérateur (int) et affichons la variable myInt. Le résultat est le même que lorsque nous affichons int.MinValue car myDouble contient une valeur plus grande que l'intervalle de int.
Il n'est pas toujours possible de prédire quelle sera la valeur d'une variable après un débordement de sa portée ! Par conséquent, utilisez des types suffisamment grands et soyez prudent lorsque vous passez à un type «plus petit».
Perte de données lors de la conversion de type
Nous allons donner un exemple de perte de données lors de la conversion de type :
L'opérateur de conversion de type peut également être utilisé en cas de conversion implicite intentionnelle. Cela contribue à la lisibilité du code, réduisant les risques d'erreurs et est considéré comme une bonne pratique par de nombreux programmeurs.
Voici quelques exemples supplémentaires de conversions de type :
Dans l'exemple ci-dessus, à la dernière ligne, nous avons une expression allant générer une erreur de compilation. En effet, nous essayons implicitement de convertir le type double en float, ce qui peut entraîner une perte de données. C# est un langage de programmation fortement typé et ne permet pas une telle appropriation de valeurs.
Forcer les exceptions de dépassement de capacité pendant le castre
Parfois, il est pratique, au lieu d'obtenir un résultat erroné, lorsqu'un type déborde lors du passage d'un type plus grand à un type plus petit, d'obtenir un avertissement du problème. Cela se fait par le mot-clef checked incluant une vérification du dépassement de capacité dans les types entiers :
Lors de l'exécution du fragment de code ci-dessus, une exception (c'est-à-dire une notification d'une erreur) de type OverflowException est levée.
Conversions explicites possibles
Les conversions explicites entre les types numériques en C# sont possibles entre n'importe quel couple parmi les types suivants :
Lors de ces conversions, des données peuvent être perdues, comme des données sur la taille du nombre ou des informations sur sa précision.
Notez que la conversion vers ou depuis une chaîne de caractères n'est pas possible via le typage.
Conversion en chaîne de caractères
Si nécessaire, nous pouvons convertir n'importe quel type de données, y compris la valeur null, en chaîne de caractères. La conversion des chaînes de caractères est effectuée automatiquement chaque fois que vous utilisez l'opérateur de concaténation (+) et que l'un des arguments n'est pas de type string. Dans ce cas, le paramètre est converti en chaîne et l'opérateur renvoie une nouvelle chaîne de caractères représentant la concaténation des deux chaînes de caractères.
Une autre façon de convertir différents objets en type string est d'appeler la méthode ToString() de la variable ou de la valeur. Elle est valable pour tous les types de données dans .NET Framework. Même l'appel de 3.ToString() est entièrement valide en C# et le résultat renverra la chaîne de caractères "3".
Jetons un oeil à plusieurs exemples de conversion de différents types de données en chaîne de caractères :
Le résultat de l'exemple est le suivant :
Somme = 10Somme = 37
Périmètre = 20. Surface = 21.
D'après les résultats, il est évident que la concaténation d'un nombre à une chaîne de caractères renvoie en résultat la chaîne de caractères suivie de la représentation textuelle du nombre. Notez que le "+" pour la concaténation de chaînes peut avoir des effets désagréables sur l'addition de nombres, car il a la même priorité que l'opérateur "+" pour l'addition mathématique. À moins que les priorités des opérations ne soient modifiées en plaçant les parenthèses, elles seront toujours exécutées de gauche à droite.
Expressions
Une grande partie du travail du programme consiste à calculer des expressions. Les expressions sont des séquences d'opérateurs, de littéraux et de variables étant calculées en une valeur d'un certain type (nombre, chaîne de caractères, objet ou autre type). Voici quelques exemples d'expressions :
Dans l'exemple, trois expressions sont définies. La première expression calcule le rayon d'un cercle. La deuxième calcule l'aire d'un cercle et la dernière trouve le périmètre. Voici le résultat du fragment ci-dessus :
569852,03456165759
351,85837720205683
Effets secondaires des expressions
Le calcul de l'expression peut avoir des effets secondaires, car l'expression peut contenir des opérateurs d'affectation intégrés, ce qui peut entraîner une augmentation ou une diminution de la valeur et l'appel de méthodes. Voici un exemple d'un tel effet secondaire :
Expressions, types de données et priorités des opérateurs Lors de l'écriture d'expressions, les types de données et le comportement des opérateurs utilisés doivent être pris en compte. Ignorer cela peut conduire à des résultats inattendus. Voici quelques exemples simples :
Dans le premier exemple, une expression divise deux entiers (écrits ainsi, 1 et deux sont des entiers) et affecte le résultat à une variable de type double. Le résultat peut être inattendu pour certaines personnes, mais c'est parce qu'elles ignorent le fait que dans ce cas, l'opérateur "/" fonctionne sur des entiers et le résultat est un entier obtenu en coupant la partie fractionnaire.
Le deuxième exemple montre que si nous voulons faire une division avec des fractions dans le résultat, il est nécessaire de convertir en float ou double au moins un des opérandes. Dans ce scénario, la division n'est plus entière et le résultat est correct.
Division par zéro
Un autre exemple intéressant est la division par 0. La plupart des programmeurs pensent que la division par 0 est une opération non valide et provoque une erreur lors de l'exécution (exception), mais cela n'est en fait vrai que pour la division entière par 0. Voici un exemple montrant que la division fractionnaire par 0 est l'infini ou NaN :
Utiliser des parenthèses pour clarifier le code
Lorsque vous travaillez avec des expressions, il est important d'utiliser des parenthèses dès qu'il existe le moindre doute sur les priorités des opérations. Voici un exemple montrant à quel point les parenthèses sont utiles :