web-dev-qa-db-fra.com

Différence entre C # et l'opérateur ternaire 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 lorsque vous traitez avec 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 5ème 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é de langage en C # manque? Le cas échéant, pourquoi n'est-il pas ajouté?

94
blackr1234

En parcourant la section C # 5 Language Specification 7.14: Conditional Operator nous pouvons voir ce qui suit:

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

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

    • Si une conversion implicite (§6.1) existe de Y en X, mais pas de X en 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

En d'autres termes: il essaie de savoir si x et y peuvent ou non être convertis en et sinon, une erreur de compilation se produit. Dans notre cas, int et string n'ont pas de conversion explicite ou implicite et ne seront donc pas compilés.

Comparez cela avec la section Spécification du langage Java 7 15.25: Opérateur conditionnel :

  • Si les deuxième et troisième opérandes ont le même type (qui peut être le type nul), 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 de boxe (§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 convertibles (§5.1.8) en types numériques, alors il y a plusieurs cas: ( [~ # ~] no [~ # ~] )
  • Sinon, les deuxième et troisième opérandes sont de types S1 et S2 respectivement. Soit T1 le type résultant de l'application de la conversion de boxe à S1, et T2 le type résultant de l'application de la conversion de boxe à 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éduire des arguments de type basés sur des arguments réels nous pouvons voir qu'il essaie de trouver un ancêtre commun qui servira de type utilisé pour l'appel qui l'atterrit avec Object. Object is un argument acceptable pour que l'appel fonctionne.

106
Jeroen Vannevel

Les réponses données sont bonnes; Je voudrais leur ajouter que cette règle de C # est la conséquence d'une directive de conception plus générale. Lorsqu'on lui a demandé de déduire le type d'une expression à partir de l'un de plusieurs choix, C # choisit le meilleur d'entre eux. Autrement dit, si vous donnez à C # des choix comme "Girafe, Mammifère, Animal", il peut choisir le plus général - Animal - ou il peut choisir le plus spécifique - Girafe - selon les circonstances. Mais il doit choisir l'un des choix qui lui ont été réellement donnés . C # ne dit jamais "mes choix sont entre le chat et le chien, donc je déduirai que l'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 plus général d'int et de chaîne, mais ni le type plus général. Plutôt que de choisir un type qui n'était pas un choix en premier lieu, comme objet, C # décide qu'aucun type ne peut être déduit.

Je note également que cela est conforme à un autre principe de conception de C #: si quelque chose ne va pas, prévenez le développeur. La langue ne dit pas "je vais deviner ce que vous vouliez dire et vous embrouiller si je peux". La langue dit "Je pense que vous avez écrit quelque chose de confus ici, et je vais vous en parler."

De plus, je note que C # ne raisonne pas de la variable à la valeur assignée , mais plutôt dans l'autre sens. 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". Au contraire, C # dit "cette expression doit avoir un type, et je dois pouvoir déduire que le type est compatible avec l'objet". Étant donné que l'expression n'a pas de type, une erreur est générée.

86
Eric Lippert

Concernant la partie génériques:

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

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

En Java, le compilateur essaie de trouver un supertype commun au lieu de convertir, donc la compilation du code implique l'utilisation implicite de caractères génériques et effacement de type ;

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

a le type de compilation de ArrayList<?> (en fait, cela peut être aussi ArrayList<? extends Serializable> ou ArrayList<? extends Comparable<?>>, selon le contexte d'utilisation, car ce sont à la fois des supertypes génériques courants) et le type d'exécution de raw 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
24
Mathieu Guindon

Dans les deux Java et C # (et 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 les deux doivent ont le même type. Dans le cas de Java, une variable int peut être convertie en Integer par autoboxing. Maintenant que Integer et String héritent à partir de Object, ils peuvent être convertis dans le même type par une simple conversion de rétrécissement.

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

6
Code-Apprentice

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

Essayer:

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