Conditional Operator Cannot Cast Implicitly

Conditional operator cannot cast implicitly?

This is a fairly frequently asked question.

In C#, we almost always reason from inside to outside. When you see

x = y;

we work out what is the type of x, what is the type of y, and whether the type of y is assignment compatible with x. But we do not use the fact that we know what the type of x is when we are working out the type of y.

That's because there might be more than one x:

void M(int x) { }
void M(string x) { }
...
M(y); // y is assigned to either int x or string x depending on the type of y

We need to be able to work out the type of an expression without knowing what it is being assigned to. Type information flows out of an expression, not into an expression.

To work out the type of the conditional expression, we work out the type of the consequence and the alternative expressions, pick the more general of the two types, and that becomes the type of the conditional expression. So in your example, the type of the conditional expression is "int", and it is not a constant (unless the condition expression is constant true or constant false). Since it is not a constant, you can't assign it to byte; the compiler reasons solely from the types, not from the values, when the result is not a constant.

The exception to all these rules is lambda expressions, where type information does flow from the context into the lambda. Getting that logic right was very difficult.

Why do I need an extra cast when using the conditional operator?

Numeric literals in C# are int, not byte. Try 0xff.

There is no implicit conversion from int to byte, and the very first statement byte red = 255; is a special case.

A constant expression of type int can be converted to sbyte, byte,
short, ushort, uint, or ulong, provided the value of the constant
expression is within the range of the destination type.

That doesn't explain why it doesn't convert the constant 255 in the second expression, does it?

It doesn't need to convert 255 in the second expression, because there is an implicit conversion from byte to int. So byte.Parse(redNode.Value) is converted to int. And so (redNode != null) ? byte.Parse(redNode.Value) : 255; is of type int - and because it's not a constant expression, there is no implicit conversion to byte anymore.

You think the error message asks you to do this:

byte red = (redNode != null) ? byte.Parse(redNode.Value) : (byte)255;

but it really was asking you to do that:

byte red = (byte)((redNode != null) ? byte.Parse(redNode.Value) : 255);

In C# why can't a conditional operator implicitly cast to a nullable type

The relevant section of the C# 3.0 spec is 7.13, the conditional operator:

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

Implicit conversion issue in a ternary condition

This is a consequence of the confluence of two characteristics of C#.

The first is that C# never "magics up" a type for you. If C# must determine a "best" type from a given set of types, it always picks one of the types you gave it. It never says "none of the types you gave me are the best type; since the choices you gave me are all bad, I'm going to pick some random thing that you did not give me to choose from."

The second is that C# reasons from inside to outside. We do not say "Oh, I see you are trying to assign the conditional operator result to an ILogger; let me make sure that both branches work." The opposite happens: C# says "let me determine the best type returned by both branches, and verify that the best type is convertible to the target type."

The second rule is sensible because the target type might be what we are trying to determine. When you say D d = b ? c : a; it is clear what the target type is. But suppose you were instead calling M(b?c:a)? There might be a hundred different overloads of M each with a different type for the formal parameter! We have to determine what the type of the argument is, and then discard overloads of M which are not applicable because the argument type is not compatible with the formal parameter type; we don't go the other way.

Consider what would happen if we went the other way:

M1( b1 ? M2( b3 ? M4( ) : M5 ( ) ) : M6 ( b7 ? M8() : M9() ) );

Suppose there are a hundred overloads each of M1, M2 and M6. What do you do? Do you say, OK, if this is M1(Foo) then M2(...) and M6(...) must be both convertible to Foo. Are they? Let's find out. What's the overload of M2? There are a hundred possibilities. Let's see if each of them is convertible from the return type of M4 and M5... OK, we've tried all those, so we've found an M2 that works. Now what about M6? What if the "best" M2 we find is not compatible with the "best" M6? Should we backtrack and keep on re-trying all 100 x 100 possibilities until we find a compatible pair? The problem just gets worse and worse.

We do reason in this manner for lambdas and as a result overload resolution involving lambdas is at least NP-HARD in C#. That is bad right there; we would rather not add more NP-HARD problems for the compiler to solve.

You can see the first rule in action in other place in the language as well. For example, if you said: ILogger[] loggers = new[] { consoleLogger, suppressLogger }; you'd get a similar error; the inferred array element type must be the best type of the typed expressions given. If no best type can be determined from them, we don't try to find a type you did not give us.

Same thing goes in type inference. If you said:

void M<T>(T t1, T t2) { ... }
...
M(consoleLogger, suppressLogger);

Then T would not be inferred to be ILogger; this would be an error. T is inferred to be the best type amongst the supplied argument types, and there is no best type amongst them.

For more details on how this design decision influences the behaviour of the conditional operator, see my series of articles on that topic.

If you are interested in why overload resolution that works "from outside to inside" is NP-HARD, see this article.

No implicit conversion when using conditional operator

This is the expected behavior.

Since no implicit conversion exists between X and Y (even if they share a common base, there is no implicit conversion between them), you need to explicitly cast (at least) one of them to the base class so that an implicit conversion exists.

A detailed explanation from the C# 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 (Section 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 (Section 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.


No implicit int - short conversion in ternary statement

The compiler has an implicit conversion from a constant expression to various primitive types (so long as the value is within the appropriate range), but here the expression isn't constant - it's just an int expression. It's pretty much the same as:

short s;
s = CallSomeMethodReturningInt32();

as far as the compiler is concerned.

There are two options - you could cast the whole expression, or cast each of the latter two operands:

short s = (EitherTrueOrFalse()) ? (short) 0 : (short) 1;

to make the overall expression type short. In this particular case, it's a pity that there isn't a numeric literal suffix to explicitly declare a short literal. Apparently the language designers did consider this, but felt it was a relatively rare situation. (I think I'd probably agree.)

The part about implicit constant conversions is from the C# 3.0 spec section 6.1.8:

6.1.8 Implicit constant expression conversions

An implicit constant
expression conversion permits the
following conversions:

  • A constant-expression (§7.18) of type
    int can be converted to type sbyte,
    byte, short, ushort, uint, or ulong,
    provided the value of the
    constant-expression is within the
    range of the destination type.
  • A
    constant-expression of type long can
    be converted to type ulong, provided
    the value of the constant-expression
    is not negative.

The conditional operator gets confused, but why?

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.

Why does ?: cause a conversion error while if-else does not?


Why can't I use uint a = b == c ? 0 : 1;?

The type of the expression b == c ? 0 : 1 is int. As shown in this table, there is no implicit conversion from int to uint, so this is not allowed.

Why can I use a = 0?

Because there is special treatment of numeric types when the value is a constant expression.

From section 6.1.9 of the C# specification:

  • A constant expression of type int can be converted to type sbyte, byte, short, ushort, uint, or ulong, provided the value of the constant-expression is within the range of the destination type.

  • A constant expression of type long can be converted to type ulong, provided the value of the constant expression is not negative.

As indicated in the first bullet a = 0 and a = 1 are both allowed because 0 and 1 are constant expressions and are valid uint values. Basically what this boils down to is that the compiler can easily determine at compile time that these conversions are valid, so it allows them.

Incidentally, if the b == c part of your first example were changed to a constant expression (e.g. true), then the whole conditional operator expression would be a constant expression and the code would compile.



Related Topics



Leave a reply



Submit