views:

74

answers:

4
public class Foo : IFooBarable {...}
public class Bar : IFooBarable {...}

So why then will this not compile...

int a = 1;
IFooBarable ting = a == 1 ? new Foo() : new Bar();

but this will...

IFooBarable ting = a == 1 ? new Foo() : new Foo();
IFooBarable ting = a == 1 ? new Bar() : new Bar();
+7  A: 

The compiler first tries to evaluate the right-hand expression:

? new Foo() : new Bar();

There's no implicit conversion between those two hence the error message. You can do this:

IFooBarable ting = a == 1 ? (IFooBarable)(new Foo()) : (IFooBarable)(new Bar());
BFree
IFooBarable ting = a == 1 ? (IFooBarable)new Foo() : new Bar() ;)
runrunraygun
+5  A: 

This is covered in section 7.13 of the C# language spec. Essentially what's killing this scenario is that there must be an implicit conversion between the types of the 2 values for the ternary operands. This conversion is considered in the abscence of the type of the variable.

So either Foo must be convertible to Bar or vice versa. Neither is so a compilation error occurs.

The latter 2 work though because they only consider 1 type (either Foo or Bar). Because they are of the same type determining the type of the expression is simple and it works fine.

JaredPar
+1  A: 

Because the type of the conditional expression is always inferred from its two parts, not from the variable to which the result is to be applied. This inference only works when the types are equal or one is reference compatible to the other. In this case, neither of the two types is reference compatible to the other one.

Jeffrey L Whitledge
+4  A: 

Just to add a bit to the correct answers posted here: there are two design guidelines that lead to this specification.

The first is that we reason from "inside to outside". When you say

double x = 2 + y;

we first work out the type of x, then the type of 2, then the type of y, then the type of (2+y), and finally, we work out whether x and (2+y) have compatible types. But we do NOT use the type of x in deciding what the type of 2, y or 2+y is.

The reason this is a good rule is because often the type of the "receiver" is exactly what we're trying to work out:

void M(Foo f) {}
void M(Bar b) {}
...
M(x ? y : z);

What are we to do here? We have to work out the type of the conditional expression in order to do overload resolution, in order to determine whether this is going to Foo or Bar. Therefore, we cannot use the fact that this is, say, going to Foo in our analysis of the type of the conditional expression! That's a chicken-and-egg problem.

The exception to this rule is lambda expressions, which do take their type from their context. Making that feature work properly was insanely complicated; see my blog series on lambda expressions vs anonymous methods if you're interested.

The second element is that we never "magic up" a type for you. When given a bunch of things from which we must deduce a type, we always deduce a type that was actually right in front of us.

In your example, the analysis goes like this:

  • work out the type of the consequence
  • work out the type of the alternative
  • find the best type that is compatible with both the consequence and the alternative
  • make sure that there is a conversion from the type of the conditional expression to the type of the thing that is using the conditional expression.

In keeping with the first point, we do not reason outside-to-inside; we don't use the fact that we know the type of the variable we're going to in order to work out the type of the expression. But the interesting thing now is that when you have

b ? new Cat() : new Dog()

we say "the type of the conditional expression is the best type in the set {Cat, Dog}". We do NOT say "the type of the conditional expression is the best type compatible with both Cat and Dog". That would be Mammal, but we don't do that. Instead, we say "the result has to be something we actually saw", and of those two choices, neither is the clear winner. If you said

b ? (Animal) (new Cat()) : new Dog()

then we have a choice between Animal and Dog, and Animal is the clear winner.

Now, note that we actually do not correctly implement the C# spec when doing this type analysis! For details of the error, see my article on it.

Eric Lippert
Great answer, very detailed!
runrunraygun
"...the best type compatible with both Cat and Dog. That would be Mammal..." Actually, the classes `Cat` and `Dog` both derive from `Carnivora`, but whatever. ;-)
Jeffrey L Whitledge