Reputation: 9756
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
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
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
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