Difference between C# and Java's ternary operator (? :)

JavaC#Ternary OperatorConditional Operator

Java Problem Overview


I am a C# newbie and I just encounter a problem. There is a difference between C# and Java when dealing with the ternary operator (? :).

In the following code segment, why does the 4th line not work? The compiler shows an error message of there is no implicit conversion between 'int' and 'string'. The 5th line does not work as well. Both Lists are objects, aren't they?

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

However, the same code works in 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

What language feature in C# is missing? If any, why is it not added?

Java Solutions


Solution 1 - Java

Looking through the C# 5 Language Specification section 7.14: Conditional Operator we can see the following:

> - If x has type X and y has type Y then

  • If an implicit conversion (§6.1) exists from X to Y, but not from Y to X, then Y is the type of the conditional expression.

> - If an implicit conversion (§6.1) exists from Y to X, but not from X to Y, then X is the type of the conditional expression.

> - Otherwise, no expression type can be determined, and a compile-time error occurs

In other words: it tries to find whether or not x and y can be converted to eachother and if not, a compilation error occurs. In our case int and string have no explicit or implicit conversion so it won't compile.

Contrast this with the Java 7 Language Specification section 15.25: Conditional Operator:

> - If the second and third operands have the same type (which may be the null type), then that is the type of the conditional expression. (NO) > - If one of the second and third operands is of primitive type T, and the type of the other is the result of applying boxing conversion (§5.1.7) to T, then the type of the conditional expression is T. (NO) > - If one of the second and third operands is of the null type and the type of the other is a reference type, then the type of the conditional expression is that reference type. (NO) > - Otherwise, if the second and third operands have types that are convertible (§5.1.8) to numeric types, then there are several cases: (NO) > - Otherwise, the second and third operands are of types S1 and S2 respectively. Let T1 be the type that results from applying boxing conversion to S1, and let T2 be the type that results from applying boxing conversion to S2.
The type of the conditional expression is the result of applying capture conversion (§5.1.10) to lub(T1, T2) (§15.12.2.7). (YES)

And, looking at section 15.12.2.7. Inferring Type Arguments Based on Actual Arguments we can see it tries to find a common ancestor that will serve as the type used for the call which lands it with Object. Object is an acceptable argument so the call will work.

Solution 2 - Java

The given answers are good; I would add to them that this rule of C# is a consequence of a more general design guideline. When asked to infer the type of an expression from one of several choices, C# chooses the unique best of them. That is, if you give C# some choices like "Giraffe, Mammal, Animal" then it might choose the most general -- Animal -- or it might choose the most specific -- Giraffe -- depending on the circumstances. But it must choose one of the choices it was actually given. C# never says "my choices are between Cat and Dog, therefore I will deduce that Animal is the best choice". That wasn't a choice given, so C# cannot choose it.

In the case of the ternary operator C# tries to choose the more general type of int and string, but neither is the more general type. Rather than picking a type that was not a choice in the first place, like object, C# decides that no type can be inferred.

I note also that this is in keeping with another design principle of C#: if something looks wrong, tell the developer. The language does not say "I'm going to guess what you meant and muddle on through if I can". The language says "I think you've written something confusing here, and I'm going to tell you about that."

Also, I note that C# does not reason from the variable to the assigned value, but rather the other direction. C# does not say "you're assigning to an object variable therefore the expression must be convertible to object, therefore I will make sure that it is". Rather, C# says "this expression must have a type, and I must be able to deduce that the type is compatible with object". Since the expression does not have a type, an error is produced.

Solution 3 - Java

Regarding the generics part:

> two > six ? new List() : new List()

In C#, the compiler tries to convert the right-hand expression parts to some common type; since List<int> and List<string> are two distinct constructed types, one can't be converted to the other.

In Java, the compiler tries to find a common supertype instead of converting, so the compilation of the code involves the implicit use of wildcards and type erasure;

> two > six ? new ArrayList() : new ArrayList()

has the compile type of ArrayList<?> (actually, it can be also ArrayList<? extends Serializable> or ArrayList<? extends Comparable<?>>, depending on use context, since they are both common generic supertypes) and runtime type of raw ArrayList (since it's the common raw supertype).

For example (test it yourself),

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

Solution 4 - Java

In both Java and C# (and most other languages), the result of an expression has a type. In the case of the ternary operator, there are two possible subexpressions evaluated for the result and both must have the same type. In the case of Java, an int variable can be converted to an Integer by autoboxing. Now since both Integer and String inherit from Object, they can be converted to the same type by a simple narrowing conversion.

On the other hand, in C#, an int is a primitive and there is not implicit conversion to string or any other object.

Solution 5 - Java

This is pretty straightforward. There is no implicit conversion between string and int. the ternary operator needs the last two operands to have the same type.

Try:

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionblackr1234View Question on Stackoverflow
Solution 1 - JavaJeroen VannevelView Answer on Stackoverflow
Solution 2 - JavaEric LippertView Answer on Stackoverflow
Solution 3 - JavaMathieu GuindonView Answer on Stackoverflow
Solution 4 - JavaCode-ApprenticeView Answer on Stackoverflow
Solution 5 - JavaChiralMichaelView Answer on Stackoverflow