Vous avez assez attendu comme ça. Voici la réponse !
Mini mais pas facile
Je rappelle la question, c’est tellement court :
Quelle sortie produira le code ci-dessous et pourquoi ?
public static void Main()
{
int i = 10;
if (i++ == i)
{
Console.WriteLine("i++ equals i ");
}
if (i == i++)
{
Console.WriteLine("i equals i++ ");
}
if (++i == i)
{
Console.WriteLine("++i equals i ");
}
if (i == ++i)
{
Console.WriteLine("i equals ++i ");
}
}
Si vous avez l’excellentissime LINQPad vous n’avez juste qu’à copier/coller le code en mode “c# program” pour obtenir la sortie suivante :
i equals i++
++i equals i
Fantastique non ?
Pourquoi ?
Reprenons, sur les quatre tests d’égalité seuls le 2d et le 3ème produise une sortie. Donc cela veut dire que ces tests sont vrais. Donc “i égal i++” et “++i égal i”. Etrange non ?
Forcément pour les deux autres tests on n’obtient aucune sortie mais on s’y attend. Une variable ne pas être égale à sa propre valeur incrémentée, par définition.
Ce qui trouble ce sont les deux tests positifs. Après tout peu de chose les différencie des deux autres.
Ce qu’il se passe est une combinaison de la position de l’incrément avec la façon dont C# évalue un test d’égalité.
Pour effectuer la comparaison la valeur de la partie de gauche de l’égalité va être chargée sur la pile en premier puis seulement ensuite la valeur de la partie de droite. C’est une partie de la réponse…
D’ailleurs il faut préciser que cela s’applique dans nombre de situations puisque C# évalue les expressions (donc aussi les assignations et les tests d’égalité) de la gauche vers la droite sauf si les règles de précédence dictent un autre chemin.
Les opérateurs de pré ou post dé/incrémentation
Je dis “les opérateurs” car tout serait identique avec la décrémentation.
Le problème principal est que ces opérateurs et leur position sont souvent très mal compris.
Confusément on pense qu’en mode préfixé (“++i”) l’opération est effectuée “avant” et que dans le mode post-fixé (“i++”) elle est effectuée “après”.
Après et avant quoi ???
Ce qu’il faut retenir c’est que peu importe la position de l’opérateur l’opération est effectuée… Ce n’est que la valeur de retour quand la variable est réclamée qui diffère.
- “i++” veut dire : “donne moi la valeur actuelle de i, puis incrémente i”.
- “++i” veut dire “incrémente i et retourne moi la valeur”
Découpons plus encore le mécanisme car ce n’est pas encore tout à fait exact, on pourrait croire encore qu’il a derrière tout cela une histoire “d’ordre” alors qu’il n’en est rien.
Forme préfixée
Sous cette forme, “++i”, C# effectue les opérations suivantes :
- “i” est évaluée pour produire la valeur de la variable
- Cette valeur est copiée dans un emplacement temporaire
- La valeur temporaire est incrémentée pour produire une nouvelle valeur (sans modifier la valeur temporaire, donc une “3eme valeur”)
- La nouvelle valeur est copiée dans la variable
- Le résultat de l’opération est la nouvelle valeur
Forme posfixée
Sous la forme post-fixée “i++”, C# effectuera les opérations suivantes :
- “i” est évaluée pour produire la valeur de la variable
- Cette valeur est copiée dans un emplacement temporaire
- La valeur temporaire est incrémentée pour produire une nouvelle valeur (sans modifier la valeur temporaire, donc une “3eme valeur”)
- La nouvelle valeur est copiée dans la variable
- Le résultat de l’opération est la valeur temporaire
Une idée fausse à balayer
Et oui c’est tout pareil, sauf la fin !
Damned ! Il ne s’agit donc pas d’une question d’ordre d’évaluation mais uniquement de savoir si la valeur retournée est la valeur temporaire du mécanisme ou bien la valeur “finale”…
Dans les deux cas “i” est bien incrémenté. Et c’est là que ça commence à coincer si on produit du code comme celui du mini quizz !
Il faut donc ici balayer une fausse idée qu’on retrouve hélas même dans des ouvrages sur C# : A aucun moment l’ordre des évènements dans le temps est différent entre la forme post ou préfixée. Il est totalement faux de dire que l’évaluation de la variable est faite avant ou après les autres évaluations.
La solution donc…
Mini quizz mais maxi explication au final. Car ce jeu des dé/incrémentations post ou préfixées est assez pervers.
“i == i++” ? Oui !
Nous avons dit que les expressions sont évaluées de gauche à droite. Donc prenons “i” qui vaut 10 par exemple. Puis regardons à droite et reprenons la séquence plus haut : la valeur de i est incrémentée mais s’agissant de l’opération post-fixée c’est la valeur temporaire, celle de “i” au départ qui est retournée. Donc si “i” vaut 10 au départ, c’est 10 qui est retourné bien que “i” valle bien “11” en mémoire. De fait, 10 == 10 est vrai.
Comme je le disais au départ, les égalités évaluent la partie gauche puis la partie droite et ce faisant stockent au fur et à mesure les valeurs dans la pile pour effectuer in fine la comparaison. De fait bien que “i” valle 11 après l’incrémentation, la partie gauche déjà évaluée à 10 ne change pas, cette valeur est déjà sur la pile. La valeur de droite posée sur la pile n’est pas la valeur de “i” mais la valeur temporaire de l’incrémentation donc 10. On a bien deux fois 10 sur la pile au moment où ces deux valeurs sont comparées. D’où la réussite du test aussi étonnant que celui puisse vous sembler.
“++i == i” ? Aussi !
Cette fois-ci le fait que les évaluations se fassent depuis la gauche est important.
La partie gauche de l’expression est évaluée, si “i” vaut 10, sa nouvelle valeur retournée est 11 qui est placé sur la pile.
La partie droite est ensuite évaluée. La valeur de la variable “i” est donc utilisée, elle vaut 11 qui est placé sur la pile.
La comparaison a lieu. Et oui, 11 vaut bien 11 !
Et les tests négatifs ?
Est-ce que “i++ == i” ? Non.
Si “i” vaut 10 au départ, la partie gauche qui est évaluée retourne la valeur temporaire de l’incrémentation donc 10 qui est placé sur la pile. Puis la partie droite est évaluée, “i” vaut 11 qui est placé sur la pile.
10 est bien différent de 11, le test échoue et ne produit aucune sortie console.
Pour “i == ++i”, le quatrième test du quizz :
Si “i” vaut 10 au départ, la partie gauche est évaluée, elle vaut 10 qui est placé sur la pile. La partie droite est évaluée, c’est la nouvelle valeur de l’incrémentation qui est retournée donc 11 placé sur la pile.
Comparaison : 10 est bien différent de 11, le test échoue sans rien sortir à la console.
Facile non ?
Une fois que l’on a bien compris comment les expressions sont évaluées et comment les opérations d’incrémentation (ou décrémentation) post et préfixées fonctionnent, interpréter le code devient un jeu d’enfant !
Mini Quizz 2
Maintenant que vous savez tout, vous allez pouvoir me dire quel est le résultat de ce mini quizz #2 :
public static void Main()
{
int[] data = { 1, 2, 3 };
int i = 1;
data[i++] = data[i] + 10;
foreach (var x in data) Console.WriteLine(x);
}
Méditez bien et …
Stay Tuned !