5

Assume two classes, both descendants of the same superclass, like this:

class MySuperClass{}
class A : MySuperClass{}
class B : MySuperClass{}

Then this assignment won't pass the compiler:

MySuperClass p = myCondition ? new A() : new B();

The compiler complains that A and B are not compatible (Type of conditional expression cannot be determined because there is no implicit conversion between 'A' and 'B' [CS0173] ). But they are both of type MySuperClass, so in my opinion this should work. Not that it's a big deal; a simple cast is all it takes to enlighten the compiler. But surely it's a snag in the C# compiler? Don't you agree?

Sascha
  • 1,210
  • 1
  • 17
  • 33
BaBu
  • 1,923
  • 2
  • 16
  • 27
  • This is a frequently asked question. See for example http://stackoverflow.com/questions/2215745 or http://stackoverflow.com/questions/3150086 or http://stackoverflow.com/questions/858080. See also my articles on the subject: http://blogs.msdn.com/b/ericlippert/archive/tags/conditional+operator/ – Eric Lippert Nov 03 '10 at 16:13

6 Answers6

10

The results of the conditional should be of the same type. They are not.

From MSDN, (?: Operator):

Either the type of first_expression and second_expression must be the same, or an implicit conversion must exist from one type to the other.

Since A and B are not the same type and you don't seem to have defined an implicit conversion, the compiler complains.

Oded
  • 489,969
  • 99
  • 883
  • 1,009
  • 3
    Yeah, try to add a cast: `p = myCondition ? (MySuperClass) new A() : (MySuperClass) new B();` – anthares Nov 03 '10 at 13:10
  • 1
    @anthares - You really just need to cast one of them and the compiler inferes the rest -> p = myCondition ? (MySuperClass) new A() : new B() – kͩeͣmͮpͥ ͩ Nov 03 '10 at 13:13
  • Sure they are. They're both MySuperClass. By the way; this works: MySuperClass p = myCondition ? (MySuperClass) new A() : new B(); So it's sufficient to cast one of the two candidates... As I said; *Not a big deal*. But still a curiosity, imho. – BaBu Nov 03 '10 at 13:16
  • 1
    BaBu - they are **not** the same type. They **inherit** from one class, but one is of _type_ `A` and the other is of _type_ `B`. By making the cast, you are giving the compiler enough information - it then casts _both_ candidates to `MySuperClass` (thus, returning the same type). – Oded Nov 03 '10 at 13:19
  • @BaBu Not exactly; one is an `A`, the other is a `B`. If both `A` and `B` implemented `IDispoable` they would be both `IDispoable` **and** `MySuperClass`, but the cast to `IDisposable` would fail -- the compiler is written to not backtrack and throw a compile error instead, which is what you're seeing – Rowland Shaw Nov 03 '10 at 13:20
4

Check out this blog for some interesting articles on why C# compiler does/doesn't do things that are 'obvious' to you. The blog is written by Eric Lippert, one of the C# compiler developers.

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Jakub Konecki
  • 45,581
  • 7
  • 87
  • 126
  • 4
    If you're going to link to his blog, at least put in the effort to find a relevant article. In this case, Eric's articles about this question would be found at http://blogs.msdn.com/b/ericlippert/archive/2010/05/27/cast-operators-do-not-obey-the-distributive-law.aspx and http://blogs.msdn.com/b/ericlippert/archive/2006/05/24/type-inference-woes-part-one.aspx among others. – Brian Nov 03 '10 at 13:27
4

Look to section 7.14 of the language specification

The second and third operands, x and y, of the ?: operator control the type of the conditional expression.

· If x has type X and y has type Y then

o 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.

o 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.

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

· If only one of x and y has a type, and both x and y, of areimplicitly convertible to that type, then that is the type of the conditional expression.

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

Essentially, the operands must be convertible to one another, not mutually convertible to some other type.

It's why you need to make an explicit cast in your example or in cases such as nullables (int? foo = isBar ? 42 : (int?)null). The declaration type does not impact the evaluation, the compiler must figure it out from the expression itself.

Community
  • 1
  • 1
Anthony Pegram
  • 123,721
  • 27
  • 225
  • 246
3

The compiler doesn't try to look for a common ancestor, so you need an explicit cast to show which ancestor you wanted to treat it as; In your case:

MySuperClass p = myCondition ? (MySuperClass)(new A()) : (MySuperClass)(new B());

This means that the conditional operator has both sides returnin gthe same type, which satisfies the compiler.

Rowland Shaw
  • 37,700
  • 14
  • 97
  • 166
3

The conditional operator (as with any other operator) has to define the type that its expression represents. In the case of the conditional operator, it has a two-step process:

  1. Are the operands of the same type? If so, that is the type of the expression.
  2. Is there an implicit conversion from one of the operand types to the other (but not in both directions)? If so, then the "other" is the type of the expression.

There's no ancestry search, as implementing that could lead down a slippery slope of ambiguity in what you, as a developer, could specify in that expression. Should everything lead to object? What about value types, which would then be implicitly boxed? What about interfaces? If there's more than one common interface between the two types, which one should be chosen?

In your case, as you've discovered, you need to upcast one of the operands to the parent type. Once you do that, rule 2.) is satisfied (there is always an implicit conversion going from a more specific type to a less specific type).

Note that you only need to apply the cast to one of the operands, not both.

Adam Robinson
  • 182,639
  • 35
  • 285
  • 343
1

Rowland Shaw summed it up pretty well, but to see why a common ancestor isn't used implicitly, consider what would happen if both classes were also to implement a particular interface. The compiler wouldn't be able to work out which to use for the type of the conditional operator, and so it would be forced to use object instead.

Will Vousden
  • 32,488
  • 9
  • 84
  • 95