Quelques trucs.
Tout d'abord, l'opérateur conditionnel est un opérateur ternaire, et non un opérateur tertiaire .
Deuxièmement, je constate que dans vos échantillons de code, les deux échantillons de code censés être équivalents ne le sont pas :
short foo;
if (isValid)
foo = -1;
else
getFoo();
n'est pas la même chose que
short foo = isValid ? (short)-1 : getFoo();
La première laisse foo non assigné si isValid est faux. La seconde assigne foo quelle que soit la valeur de isValid.
Je suppose que vous vouliez dire
short foo;
if (isValid)
foo = -1;
else
foo = getFoo();
et qu'en plus, getFoo() renvoie court.
La question est de savoir pourquoi la conversion dans l'opérateur conditionnel sans le cast de type est illégale mais dans la conséquence de l'instruction if est légale.
Il est légal dans l'instruction if parce que la section 6.1.9 de la spécification indique :
Une expression constante de type int peut être convertie en type sbyte, byte, short, ushort, uint ou ulong, à condition que la valeur de l'expression constante soit comprise dans la plage du type de destination.
-1 est une expression constante de type int qui est dans l'intervalle de short, elle peut donc être convertie en short implicitement.
Alors pourquoi la forme d'expression conditionnelle est-elle fausse ?
La première chose que nous devons établir clairement est la règle selon laquelle le type de l'expression conditionnelle est déterminé à partir de son contenu et non de son contexte . Le type de l'expression du côté droit d'une affectation ne dépend pas de ce à quoi elle est affectée ! Supposons que vous ayez
short M(short x){...}
int M(int x){...}
short y = M(-1);
Je ne pense pas que vous vous attendiez à ce que la résolution de surcharge dise "bien, je choisirais normalement M(int) parce que -1 est un int, mais non, je vais choisir M(short) à la place parce que sinon l'affectation ne fonctionnera pas". La résolution de surcharge ne sait rien sur où va le résultat . Son rôle est de déterminer quelle est la bonne surcharge en fonction des arguments donnés, et non en fonction du contexte de l'appel.
La détermination du type de l'expression conditionnelle fonctionne de la même manière. Nous ne regardons pas le type vers lequel elle va, nous regardons les types qui sont dans l'expression.
OK, nous avons donc établi que le fait que cette expression soit affectée à court n'est pas pertinent pour déterminer le type de l'expression. Mais cela laisse toujours la question "Pourquoi le type de l'expression conditionnelle est int plutôt que short ?"
C'est une très bonne question. Passons à la spécification.
Les deuxième et troisième opérandes, x et y, de l'opérateur ? : contrôlent le type de l'expression conditionnelle.
Si a le type X et y le type Y alors :
Si une conversion implicite existe de X à Y, mais pas de Y à X, alors Y est le type de l'expression conditionnelle.
Si une conversion implicite existe de Y à X, mais pas de X à Y, alors X est le type de l'expression conditionnelle.
Sinon, aucun type d'expression ne peut être déterminé, et une erreur de compilation se produit.
Dans ce cas, les opérandes ont tous deux un type. (Le verbiage à propos de "si x a un type..." est pour le cas où vous avez null ou un lambda ; ceux-ci n'ont pas de type). Le premier opérande est de type int, le second est de type short.
Une conversion implicite existe de short à int, mais pas de int à short. Par conséquent, le type de l'expression conditionnelle est int, qui ne peut pas être affecté à short.
On pourrait dire que cet algorithme n'est pas aussi bon qu'il pourrait l'être. Nous pourrions grandement compliquer l'algorithme pour traiter tous les cas où il y a deux types "candidats" possibles -- dans ce cas, int et short sont tous deux des candidats plausibles car les deux branches sont convertibles en int et en short. lorsqu'ils sont considérés comme des expressions spécifiques, plutôt que simplement comme ayant des types . Nous pourrions dire dans ce cas que le plus petit des deux types était le type préféré.
(Parfois, en C#, on dit que le plus général de deux types est le meilleur type, mais dans ce cas, vous voudriez que nous choisissions le plus spécifique. Le langage n'est pas cohérent dans cet aspect particulier de la conception, malheureusement ; personnellement, je préférerais que nous choisissions toujours le plus spécifique, mais il y a des scénarios d'inférence de type où cela serait un changement de rupture maintenant).
J'ai envisagé de le faire en 2006. Lors de la conception du comportement de LINQ dans les situations où il y a plusieurs types à choisir et où l'un d'eux doit être sélectionné comme "le meilleur", nous avons remarqué que l'opérateur conditionnel devait déjà résoudre ce problème et que, de plus, dans C# 2, il n'était pas réellement implémenté conformément à la spécification. Un long débat a eu lieu à ce sujet et nous avons fini par apporter quelques modifications mineures à la spécification de l'opérateur conditionnel afin de le rendre plus conforme à son comportement implémenté (et souhaité). Cependant, nous avons décidé de ne pas prendre la décision de modifier l'algorithme pour qu'il utilise le plus petit des deux types possibles, alors qu'il en existe plusieurs.
Pour quelques réflexions sur ce problème, voir mes posts de 2006 à ce sujet :