94 votes

Différence entre l'opérateur ternaire de C# et de Java ( ? :)

Je suis un débutant en C# et je viens de rencontrer un problème. Il y a une différence entre C# et Java lorsqu'il s'agit de l'opérateur ternaire ( ? : ).

Dans le segment de code suivant, pourquoi la 4e ligne ne fonctionne-t-elle pas ? Le compilateur affiche un message d'erreur de there is no implicit conversion between 'int' and 'string' . La 5e ligne ne fonctionne pas aussi bien. Les deux List sont des objets, n'est-ce pas ?

int two = 2;
double six = 6.0;
Write(two > six ? two : six); //param: double
Write(two > six ? two : "6"); //param: not object
Write(two > six ? new List<int>() : new List<string>()); //param: not object

Cependant, le même code fonctionne en Java :

int two = 2;
double six = 6.0;
System.out.println(two > six ? two : six); //param: double
System.out.println(two > six ? two : "6"); //param: Object
System.out.println(two > six ? new ArrayList<Integer>()
                   : new ArrayList<String>()); //param: Object

Quelle fonctionnalité du langage C# manque-t-elle ? Le cas échéant, pourquoi n'est-elle pas ajoutée ?

106voto

Jeroen Vannevel Points 18676

En regardant à travers le Section 7.14 des spécifications du langage C# 5 : Opérateur conditionnel nous pouvons voir ce qui suit :

  • Si x a le type X et y le type Y, alors

    • Si une conversion implicite (§6.1) existe de X à Y, mais pas de Y à X, alors Y est le type de l'expression conditionnelle. expression conditionnelle.

    • Si une conversion implicite (§6.1) existe de Y à X, mais pas de X à Y, alors X est le type de l'expression conditionnelle. expression conditionnelle.

    • Sinon, aucun type d'expression ne peut être déterminé et une erreur de compilation se produit.

En d'autres termes, il essaie de trouver si oui ou non x et y peuvent être convertis en l'un l'autre et si ce n'est pas le cas, une erreur de compilation se produit. Dans notre cas int et string n'ont pas de conversion explicite ou implicite, donc ils ne compileront pas.

Comparez cela avec le Section 15.25 des spécifications du langage Java 7 : opérateur conditionnel :

  • Si les deuxième et troisième opérandes ont le même type (qui peut être le type null), alors c'est le type de l'expression conditionnelle. ( NON )
  • Si l'un des deuxième et troisième opérandes est de type primitif T, et que le type de l'autre est le résultat de l'application de la conversion par boîte (§5.1.7) à T, alors le type de l'expression conditionnelle est T. ( NON )
  • Si l'un des deuxième et troisième opérandes est de type nul et que le type de l'autre est un type de référence, alors le type de l'expression conditionnelle est ce type de référence. ( NON )
  • Sinon, si les deuxième et troisième opérandes ont des types qui sont convertibles (§5.1.8) en types numériques, alors il y a plusieurs cas : ( NON )
  • Sinon, les deuxième et troisième opérandes sont respectivement de type S1 et S2. Soit T1 le type qui résulte de l'application de la conversion en boîte à S1, et soit T2 le type qui résulte de l'application de la conversion en boîte à S2.
    Le type de l'expression conditionnelle est le résultat de l'application de la conversion de capture (§5.1.10) à lub(T1, T2) (§15.12.2.7). ( OUI )

Et, en regardant section 15.12.2.7. Déduction des arguments de type à partir des arguments réels nous pouvons voir qu'il essaie de trouver un ancêtre commun qui servira de type utilisé pour l'appel, ce qui le fait aboutir à Object . Object est un argument acceptable pour que l'appel fonctionne.

86voto

Eric Lippert Points 300275

Les réponses données sont bonnes ; j'y ajouterais que cette règle du C# est une conséquence d'une directive de conception plus générale. Lorsqu'on vous demande de déduire le type d'une expression à partir d'un choix parmi plusieurs, C# choisit le meilleur d'entre eux . En d'autres termes, si vous donnez à C# des choix tels que "Girafe, Mammifère, Animal", il peut choisir le plus général -- Animal -- ou le plus spécifique -- Girafe -- en fonction des circonstances. Mais il doit choisir un des choix qui lui a été donné . C# ne dit jamais "mes choix sont entre Chat et Chien, donc je vais déduire qu'Animal est le meilleur choix". Ce n'était pas un choix donné, donc C# ne peut pas le choisir.

Dans le cas de l'opérateur ternaire, C# essaie de choisir le type le plus général entre int et string, mais aucun n'est le type le plus général. Plutôt que de choisir un type qui n'était pas un choix en premier lieu, comme object, C# décide qu'aucun type ne peut être déduit.

Je note également que cela est conforme à un autre principe de conception du C# : si quelque chose ne semble pas correct, il faut le dire au développeur. Le langage ne dit pas "Je vais deviner ce que vous vouliez dire et me débrouiller si je peux". Le langage dit "Je pense que vous avez écrit quelque chose de confus ici, et je vais vous en parler".

Je note également que le C# ne raisonne pas à partir de l'élément variable à la valeur assignée mais plutôt dans l'autre sens. Le C# ne dit pas "vous assignez à une variable objet, donc l'expression doit être convertible en objet, donc je vais m'assurer qu'elle l'est". C# dit plutôt "cette expression doit avoir un type, et je dois être capable de déduire que le type est compatible avec object". Comme l'expression n'a pas de type, une erreur est produite.

24voto

retailcoder Points 3056

En ce qui concerne la partie générique :

two > six ? new List<int>() : new List<string>()

En C#, le compilateur essaie de convertir l'expression de droite fait partie d'un type commun ; puisque List<int> et List<string> sont deux types construits distincts, l'un ne peut être converti en l'autre.

En Java, le compilateur essaie de trouver une supertype commune au lieu de convertir, donc la compilation du code implique l'utilisation implicite de caractères de remplacement et effacement de type ;

two > six ? new ArrayList<Integer>() : new ArrayList<String>()

a le type de compilation de ArrayList<?> (en fait, cela peut aussi être ArrayList<? extends Serializable> ou ArrayList<? extends Comparable<?>> en fonction du contexte d'utilisation, puisqu'il s'agit de supertypes génériques communs) et le type d'exécution de l'image brute. ArrayList (puisque c'est le supertype brut commun).

Par exemple (testez-le vous-même) ,

void test( List<?> list ) {
    System.out.println("foo");
}

void test( ArrayList<Integer> list ) { // note: can't use List<Integer> here
                                 // since both test() methods would clash after the erasure
    System.out.println("bar");
}

void test() {
    test( true ? new ArrayList<Object>() : new ArrayList<Object>() ); // foo
    test( true ? new ArrayList<Integer>() : new ArrayList<Object>() ); // foo 
    test( true ? new ArrayList<Integer>() : new ArrayList<Integer>() ); // bar
} // compiler automagically binds the correct generic QED

6voto

Code-Apprentice Points 18086

En Java et en C# (et dans la plupart des autres langages), le résultat d'une expression a un type. Dans le cas de l'opérateur ternaire, il existe deux sous-expressions possibles évaluées pour le résultat et toutes deux doivent avoir le même type. Dans le cas de Java, une int peut être convertie en une variable Integer par autoboxing. Maintenant que les deux Integer et String hériter de Object ils peuvent être convertis au même type par une simple conversion de rétrécissement.

D'autre part, en C#, un fichier int est une primitive et il n'y a pas de conversion implicite en string ou tout autre object .

5voto

ChiralMichael Points 459

C'est assez simple. Il n'y a pas de conversion implicite entre string et int. L'opérateur ternaire a besoin que les deux derniers opérandes aient le même type.

Essayez :

Write(two > six ? two.ToString() : "6");

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X