Nelson Rothermel
Nelson Rothermel

Reputation: 9756

Is the Null-Conditional operator optimized across consecutive usages sites or does it result in duplicate checks?

Does using the null-conditional operator duplicate null checks? For example

var x = instance?.Property1;
var y = instance?.Property2;

Does that get compiled into this:

if (instance != null)
{
  var x = instance.Property1;
  var y = instance.Property2;
}

Or this?

if (instance != null)
{
  var x = instance.Property1;
}

if (instance != null)
{
  var y = instance.Property2;
}

If the former, does it make a difference if there is other code in between both lines? In other words, how smart is the compiler/optimizer?

Upvotes: 8

Views: 215

Answers (3)

Cheng Chen
Cheng Chen

Reputation: 43523

The compiler is rigorous, think about this code.

class Tricky
{
    public int Property1
    {
        get
        {
            Program.instance = null;
            return 1;
        }
    }

    public int Property2
    {
        get
        {
            return 2;
        }
    }
}

class Program
{
    public static Tricky instance = new Tricky();

    public static void Main(string[] arg)
    {
        var x = instance?.Property1;
        var y = instance?.Property2;
        //what do you think the values of x,y
    }
}

The expected result is : x == 1, y is null. But if the compiler optimizes the code using one if statement, it throws a NullReferenceException. Which means, using one if statement, is not a smart optimization, it's not an optimization, because it's WRONG.

Upvotes: 5

Martin
Martin

Reputation: 664

It does two separate If..Else checks for the assignment using null conditional operator in your case. Below is a disassembled sample code

Source Code:

public class nulltest
{
    public void test()
    {
        var instance = new testclass();
        var x = instance?.prop1;
        var y = instance?.prop2;
    }
}

public class testclass
{
    public int prop1;
    public int prop2;
}

Disassembled code (ILSpy):

public class nulltest
{
    public void test()
    {
        testclass testclass = new testclass();
        if (testclass == null)
        {
            int? arg_20_0 = null;
        }
        else
        {
            new int?(testclass.prop1);
        }
        if (testclass == null)
        {
            int? arg_3A_0 = null;
        }
        else
        {
            new int?(testclass.prop2);
        }
    }
}

I used int as the type of the property but the above should hold true for any other case.

Upvotes: 5

Enigmativity
Enigmativity

Reputation: 117124

The compiler appears to be quite ignorant of this.

The code:

var x = instance?.Property1;
var y = instance?.Property2;

...compiles as non-optimized to:

IL_0000:  nop         
IL_0001:  newobj      UserQuery+Class..ctor
IL_0006:  stloc.0     // instance
IL_0007:  ldloc.0     // instance
IL_0008:  brtrue.s    IL_000D
IL_000A:  ldnull      
IL_000B:  br.s        IL_0013
IL_000D:  ldloc.0     // instance
IL_000E:  ldfld       UserQuery+Class.Property1
IL_0013:  stloc.1     // x
IL_0014:  ldloc.0     // instance
IL_0015:  brtrue.s    IL_001A
IL_0017:  ldnull      
IL_0018:  br.s        IL_0020
IL_001A:  ldloc.0     // instance
IL_001B:  ldfld       UserQuery+Class.Property2
IL_0020:  stloc.2     // y
IL_0021:  ret         

Class..ctor:
IL_0000:  ldarg.0     
IL_0001:  call        System.Object..ctor
IL_0006:  nop         
IL_0007:  ret         

...and to optimized as:

IL_0000:  newobj      UserQuery+Class..ctor
IL_0005:  dup         
IL_0006:  dup         
IL_0007:  brtrue.s    IL_000C
IL_0009:  pop         
IL_000A:  br.s        IL_0012
IL_000C:  ldfld       UserQuery+Class.Property1
IL_0011:  pop         
IL_0012:  dup         
IL_0013:  brtrue.s    IL_0017
IL_0015:  pop         
IL_0016:  ret         
IL_0017:  ldfld       UserQuery+Class.Property2
IL_001C:  pop         
IL_001D:  ret         

Class..ctor:
IL_0000:  ldarg.0     
IL_0001:  call        System.Object..ctor
IL_0006:  ret         

Both clearly with two branch checks.

Upvotes: 6

Related Questions