views:

79

answers:

6

I'm experiencing unexpected compiler errors with this code:

bool b = true; //or false
StringBuilder builder = ...; // a string builder filled with content
IVersePart vp = b ? (DualLanguageVersePart)builder : (VersePart)builder;

Both DualLanguageVersePart and VersePart implement the IVersePart interface. Both DualLanguageVersePart and VersePart have an explicit cast operator form StringBuilder.

Since both classes implement the interface that's the type of vp, I would expect this to work flawlessly, or at least compile properly. Instead the compiler reports that no implicit conversion can be done between the two types.

Why is this not working?

+1  A: 

I've had this issue before, ternary operator requires both types of the true result or false result either be the same type, or you cast them to the same type.

Jimmy Hoffa
Not true. It's sufficient for one operand's type to be a subtype of the other.
Ben Voigt
+2  A: 

Both parts have to have the same type, so try this:

IVersePart vp = b ? 
  (IVersePart)(DualLanguageVersePart)builder :
  (IVersePart)(VersePart)builder;

The C# compiler is fussier about this than the C++ compiler :)

Blindy
Decent C++ compiler won't allow that as well. At least GCC won't.
Ihor Kaharlichenko
Why would you cast them twice? Just cast them once to IVersePart and it's good..
Jimmy Hoffa
No, you can't, because the explicit cast operator is creating a new instance of either DualLanguageVersePart or VersePart.
Excel20
A: 

The ternary operator needs to be able to return one type, as it's just a single statement. So when you have the two parts of the operator returning different types, the compiler will attempt to allow this by quietly converting one to the other. You don't have a conversion from DualLanguageVersePart to VersePart, or vice versa.

One simple fix is to just add in a second cast to IVersePart:

IVersePart vp = b ? (IVersePart)(DualLanguageVersePart)builder : (IVersePart)(VersePart)builder;
Matt Greer
A: 

The ternary operate requires the true and false to return the same type. Here's is one way to get around this, kind of sloppy though...

IVersePart vp = b 
    ? (IVersePart)((DualLanguageVersePart)builder) 
    : (IVersePart)((VersePart)builder); 
Jerod Houghtelling
+2  A: 

This is a bad design. Cast operators should not be used like that.

It would be better to have a ctor to handle this (as you are, in fact trying to construct a new object)

IVersePart vp = new DualLanguageVersePart(builder);

Alternately, you could use a factory:

IVersePart vp = VersePart.DualOrSingluar(builder, b);
James Curran
A: 

The result type of any operator (other than operator implicit) is determined by the operands and not by context.

Contrary to most of the wisdom being dispensed here, both arguments don't have to be the same type. If one type is derived from the other then the ternary operator result will be the base type. But in your example there are multiple common base types: at least Object and IVersePart, and the C# language doesn't make the compiler try to figure out which is better.

Ben Voigt
But there will always be `Object` and some interface common in these scenarios. How is that any different than always requiring the same type in both operands?
Blindy
If one operand's type `T` is a subtype of the other operand's type `S`, then `S` is a superset of all shared (at compile time) types and therefore the best conversion. In this case it means that casting just one of the operands to `IVersePart` is sufficient to resolve the ambiguity. Try it.
Ben Voigt