The inline if statement in C# : Surprise !

NET%20LogoHi, I got a strange issue today I posted on stackoverflow.

The Issue

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();
Type of conditional expression cannot be determined because there is no implicit conversion between 'B' and 'C'

WHY ? Why does it work in classical if statement and not in inline if statement ?

The Answer

1775769

Here’s his answer :

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.

Behind the scene

But the answer wasn’t totally complete for me. So I took my lovely decompiler and anaylyze what’s generated in the case of an inline and classical if. The code is “self explanatory” and I’ve commented each MSIL instruction to make it readable.

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;
}

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 
}

Now same thing with an additional cast :

static void ClassicalIf(bool condition)
{
    object i = 0;
    if (condition)
        i = new object();
    else
        i = 2;
}
    
static void InlineIf(bool condition)
{
    object i = condition ? new object() : 2;
}

For the inline if :

.method private hidebysig static void InlineIf(bool condition) cil managed
{
    .maxstack 1
    .locals init (
        [0] object i)
    L_0000: nop 				
    L_0001: ldarg.0 			-- Load argument '0' onto the stack
    L_0002: brtrue.s L_000c     -- Branch to L_000c if value is non-zero (true)
    L_0004: ldc.i4.2            -- Push 2 onto the stack
    L_0005: box int32           -- Boxing
    L_000a: br.s L_0011         -- Branch to L_0008
    L_000c: newobj instance void-- Create new object  
    L_0011: nop                                                     
    L_0012: stloc.0 			-- Pop from stack into local variable 0
    L_0013: 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] object i,
        [1] bool CS$4$0000)
    L_0000: nop 					-- Push 0 onto the stack
    L_0001: ldc.i4.0                -- Pop from stack into local variable '0'
    L_0002: box int32               -- Boxing
    L_0007: stloc.0                 -- Pop from stack into local variable 0
    L_0008: ldarg.0                 -- Load argument '0' onto the stack
    L_0009: ldc.i4.0                -- Push 0 onto the stack 
    L_000a: ceq                     -- Push 1 if value1 equals value2 (on stack), else push 0.
    L_000c: stloc.1                 -- Pop from stack into local variable '1'
    L_000d: ldloc.1                 -- Load local variable '1' onto stack.
    L_000e: brtrue.s L_0018         -- Branch to L_0018 if value is non-zero (true)
    L_0010: newobj instance void [ms-- Create new object
    L_0015: stloc.0                 -- Pop from stack into local variable '0'
    L_0016: br.s L_001f             -- Branch to L_001f
    L_0018: ldc.i4.2                -- Push 2 onto the stack 
    L_0019: box int32               -- Boxing
    L_001e: stloc.0 				-- Pop from stack into local variable '0'
    L_001f: ret 
}

Conclusion

1) So I will also try to use inline if as possible. Less instructions (so CPU) and less memory usage.
2) Also it’s now crystal clear when we see how an inline if is converted to MSIL 🙂

Catch you next time and keep it bug free !

Published by Emmanuel Istace

Musician, Software developer and guitar instructor. https://emmanuelistace.be/

One thought on “The inline if statement in C# : Surprise !

  1. The engaging nature of distinct games serves to make
    such games well-known in common but at the
    very same time, games that are much more engaging are typically far more popular.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: