15

I am wondering why this line of code doesn't compile:

ILogStuff Logger = (_logMode) ? new LogToDisc() : new LogToConsole();

Note that both classes LogToDiscand LogToConsole implement ILogStuff, and _logMode is a boolean variable. The error message I get is:

Error 3: Type of conditional expression cannot be determined because there is no implicit conversion between 'xxx.LogToDisc' and 'xxx.LogToConsole'

But why should there be one? What am I missing?

MrWhite
  • 43,179
  • 8
  • 60
  • 84
EluciusFTW
  • 2,565
  • 6
  • 42
  • 59
  • Using [ReSharper](http://www.jetbrains.com/resharper/) would have saved you from even asking this as a question here ;-) – Uwe Keim Apr 16 '14 at 12:00
  • 9
    Yes, well, but ReSharper is not freeware as far as I know ... – EluciusFTW Apr 16 '14 at 12:37
  • So what? Paying a few hundered dollars is (IMO) _way_ cheaper than even spending half an hour on Stack Overflow and asking for questions during your working hours. – Uwe Keim Apr 16 '14 at 15:03
  • 6
    Well, if you're a professional programmer in a big company, maybe. But I find your comment a bit disrespectful to all freeware/ non-commercial programmers like myself. Besides, if some tool would have shown me the error, I'd still not have learned as much as I have now, i.e., the particular reasons WHY it does and should not work. – EluciusFTW Apr 30 '14 at 11:55

5 Answers5

17

There is not implicit conversion available for ternary operator. You need to cast the returned object by ternary operator to ILogStuff, This is very well explain in Eric Lippert's answer for the question Implicit conversion issue in a ternary condition

ILogStuff Logger = (_logMode) ? (ILogStuff) new LogToDisc() : (ILogStuff) new LogToConsole();

From chapter 7.13 of the C# Language Specification:

The second and third operands of the ?: operator control the type of the conditional expression. Let X and Y be the types of the second and third operands. Then,

  • If X and Y are the same type, then this is the type of the conditional expression.
  • Otherwise, 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.
  • Otherwise, 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.
Community
  • 1
  • 1
Adil
  • 146,340
  • 25
  • 209
  • 204
9

You need to cast to the interface:

ILogStuff Logger = (_logMode) ? (ILogStuff)new LogToDisc() : new LogToConsole();

The specification describes the behaviour of the conditional operator:

7.14 Conditional operator

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

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

There is no implicit conversion between LogToDisc and LogToConsole in either direction, so the compilation fails. If you fix one of the types to ILogStuff the implicit conversion from the other type will exist.

Lee
  • 142,018
  • 20
  • 234
  • 287
3

The message is correct, there is no implicit conversion between those two types, they just share the common interface. But of course shared parent do not imply possiblity of casting, in the same way as int is not implicty covertible into string although both have common parent - Object.

The ternary operator expects that the result type of both possible values will be the same - in terms of possibility to make an implicit cast between them. So you must tell him, that the first return value is of type ILogStuff:

ILogStuff Logger = (_logMode) ? (ILogStuff)new LogToDisc() : new LogToConsole();

Then, the second possible value is proper one - there exists implicit conversion between LogToConsole type and ILogStuff interface.

Konrad Kokosa
  • 16,563
  • 2
  • 36
  • 58
  • Yeah, get what you're saying. Thanks. Just seems weird when `ILogStuff Logger = new LogToConsole()` compiles just fine, and you think you use the ternary expression just to choose one of the concrete classes. In this logic, they don't need to have anything to do with eachother, except implement `ILogStuff` – EluciusFTW Apr 16 '14 at 09:17
  • 1
    @ToaoG The key is that it's an expression. The expression needs to make sense on its own regardless of what you're assigning it to. Expressions should have a determinable type at compile time. – Cruncher Apr 16 '14 at 12:48
3

The expression must return a common type of both implementations. By explicitly casting the instances to the interface, the expression compiles:

ILogStuff Logger = (_logMode) ? 
    (ILogStuff)new LogToDisc() : 
    (ILogStuff)new LogToConsole();
Carsten
  • 11,287
  • 7
  • 39
  • 62
0

Adil has provided the section that determines this behaviour, but I'd like to explain why this behaviour is sensible.

bool ? val1 : val2

This is an expression. Expressions need to have a type that is determinable at compile time. This makes it faster, and catches errors sooner.

Now, if:

val is an instance of MyObject1 which extends SomeParent and implements MyInterface,
and val2 is and instance of MyObject2 which extends SomeParent and implements MyInterface

How do we determine the compile-time type of this expression? We could try to find a common type between MyObject1 and MyObject2. What's the most obvious solution? Do you call it a SomeParent or a MyInterface? and If they had 2 Interfaces, which one would you pick?

The problem is this is a mess, and would require some pretty contrived rules(and in fact there are more examples that would be less clear) that at the end of the day, would be less intuitive than the current definition.

Cruncher
  • 7,641
  • 1
  • 31
  • 65