0

I got a strange behaviour and I would like to understand it. But I haven't found good answers on the web :(

Here's the situation, I've abstracted the name and logic to focus on the issue. Got 3 types, A, B and C. B & C have implicit operators defined to convert to A object.

public class A
{
    public static implicit operator A(B input){ /* Convert B to A */ }
    public static implicit operator A(C input) { /* Convert C to A*/ }
}

public class B { }
public class C { }

Then, when I do this, the code compile and work fine :

A myObject = null;
if (condition)
    myObject = new B();
else
    myObject = new C();

But when I write the same logic with an inline if, I got an error :

A myObject = condition ? new B() : new C();

The error :

Type of conditional expression cannot be determined because there is no implicit conversion between 'B' and 'C'

Do you have any idea about this behaviour ?

Thank's in advance for your time.

Best regards and keep it bug free !

Emmanuel Istace
  • 1,209
  • 2
  • 14
  • 32

4 Answers4

3

Do you have any idea about this behaviour ?

Absolutely. The type of a conditional operator expression must either be the type of the second operand or the type of the third operand. (And if those two types aren't the same, exactly one of the types has to be implicitly convertible to the other.) The compiler doesn't try to find a "lower common denominator" type, and the use of the result isn't important either (the compiler doesn't "notice" that you're assigning the result to a variable of type A).

You can fix this yourself by just explicitly casting either operand:

A myObject = condition ? (A) new B() : new C();

or

A myObject = condition ? new B() : (A) new C();

Note that this isn't limited to user-defined conversion operators; the same is true for simple reference conversions based on derived classes:

Button x = new Button();
String y = "foo";
object z = condition ? x : y; // Compile-time error

See section 7.14 of the C# specification for more details.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
1

The error is clear

there is no implicit conversion between 'B' and 'C'

You have defined implicit conversion between A and B and A and C. But there is no implicit conversion between B and C.

Conditional operator requires:

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.

Habib
  • 219,104
  • 29
  • 407
  • 436
  • Thank's but my question was more about the why. I was thinking inline statements were just a syntaxic stuff but were then compiled as classical branching in MSIL. – Emmanuel Istace Sep 27 '13 at 13:53
  • 1
    Its not syntactic sugar. See http://stackoverflow.com/questions/17328641/ternary-operator-is-twice-as-slow-as-an-if-else-block – Habib Sep 27 '13 at 13:55
0

You may have a look at the remarks in ?: Operator (C# Reference) It states

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.

Giannis Paraskevopoulos
  • 18,261
  • 1
  • 49
  • 69
0

@Jon Skeet : After having a look at the generated MSIL, here's what I found and it's really interresting. Here's the two methods I've write to test :

static void ClassicalIf(bool condition)
{
    int i = 0;
    if (condition)
        i = 1;
    else
        i = 2;
}

static void InlineIf(bool condition)
{
    int i = condition ? 1 : 2;
}

Here's the MSIL with comments, so anyone can understand what's done and also why indeed a implicit conversion is required for the inline syntax.

For the inline if :

.method private hidebysig static void InlineIf(bool condition) cil managed
{
    .maxstack 1
    .locals init (
        [0] int32 i)
    L_0000: nop 
    L_0001: ldarg.0         -- Load argument '0' onto the stack
    L_0002: brtrue.s L_0007 -- Branch to L_0007 if value is non-zero
    L_0004: ldc.i4.2        -- Push 2 onto the stack
    L_0005: br.s L_0008     -- Branch to L_0008
    L_0007: ldc.i4.1        -- Push 1 onto the stack
    L_0008: nop 
    L_0009: stloc.0         -- Pop from stack into local variable 0
    L_000a: ret 
}

And here's the one for the "normal" if :

.method private hidebysig static void ClassicalIf(bool condition) cil managed
{
    .maxstack 2
    .locals init (
        [0] int32 i,
        [1] bool CS$4$0000) -- Additional bool for if
    L_0000: nop 
    L_0001: ldc.i4.0        -- Push 0 onto the stack
    L_0002: stloc.0         -- Pop from stack into local variable '0'
    L_0003: ldarg.0         -- Load argument '0' onto the stack
    L_0004: ldc.i4.0        -- Push 0 onto the stack 
    L_0005: ceq             -- Push 1 if value1 equals value2 (on stack), else push 0.
    L_0007: stloc.1         -- Pop from stack into local variable '1'
    L_0008: ldloc.1         -- Load local variable '1' onto stack.
    L_0009: brtrue.s L_000f -- Branch to L_000f if value is non-zero
    L_000b: ldc.i4.1        -- Push 1 onto the stack 
    L_000c: stloc.0         -- Pop from stack into local variable '0'
    L_000d: br.s L_0011     -- Branch to L_0011
    L_000f: ldc.i4.2        -- Push 2 onto the stack 
    L_0010: stloc.0         -- Pop from stack into local variable '0'
    L_0011: ret 
}

So I will also try to use inline if as possible. Less instructions (so CPU) and less memory usage.

Emmanuel Istace
  • 1,209
  • 2
  • 14
  • 32