One of the subtle design rules of C# is that C# never infers a type that wasn't in the expression to begin with. Since Animal
is not in the expression d ?? c
, the type Animal
is not a choice.
This principle applies everywhere that C# infers types. For example:
var x = new[] { dog1, dog2, dog3, dog4, cat }; // Error
The compiler does not say "this must be an array of animals", it says "I think you made a mistake".
This is then a specific version of the more general design rule, which is "give an error when a program looks ambiguous rather than making a guess that might be wrong".
Another design rule that comes into play here is: reason about types from inside to outside, not from outside to inside. That is, you should be able to work out the type of everything in an expression by looking at its parts, without looking at its context. In your example, Animal
comes from outside the ??
expression; we should be able to figure out what the type of the ??
expression is and then ask the question "is this type compatible with the context?" rather than going the other way and saying "here's the context -- now work out the type of the ??
expression."
This rule is justified because very often the context is unclear. In your case the context is very clear; the thing is being assigned to Animal
. But what about:
var x = a ?? b;
Now the type of x
is being inferred. We don't know the type of the context because that's what we're working out. Or
M(a ?? b)
There might be two dozen overloads of M
and we need to know which one to pick based on the type of the argument. It is very hard to reason the other way and say "the context could be one of these dozen things; evaluate a??b
in each context and work out its type".
That rule is violated for lambdas, which are analyzed based on their context. Getting that code both correct and efficient was very difficult; it took me the better part of a year's work. The compiler team can do more features, faster and better, by not taking on that expense where it is not needed.
??
can't infer the common base class, but I have to nitpick a little; readers should take note that adding the required extra casts as hints to??
does defeat the type system a little will increase the chance of runtime failure. For example, if you didAnimal a = d ?? (Animal) c;
andc
was something more generic like anobject
the compiler wouldn't be able to warn you. FWIW, the more verboseif(obj == null) ... else
syntax does not require a cast, it may be a better fit in some cases. – Rascality