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.