If the title makes sense, please skip below to my Questions section. If not, here's some context.
I am writing some code, and it happened to use the null-coalescing operator. My code:
_handle = handle ?? throw new ArgumentNullException (nameof(handle));
Both _handle
and handle
are of type class Handle
(that I've written).
I decided to investigate the IL. Here's what it spits out:
...
IL_0002: ldarg.1 // handle
IL_0003: dup
IL_0004: brtrue.s IL_0012 // transfer to line that stores into _handle
IL_0006: pop
IL_0007: ldstr "handle"
IL_000c: newobj instance void [mscorlib]System.ArgumentNullException::.ctor(string)
IL_0011: throw
IL_0012: stfld class Sandbox.V1.Handle Sandbox.V1.Spade::_handle
...
I see here that the brtrue.s
(or it could've been a brfalse.s
with slightly different code) instruction (IL_0004) doesn't really check that the handle
reference equals (or not) null (ldnull
).
Instead it calls brfalse
directly on the handle
reference.
In other words, it did not do something along the lines of this at the beginning:
...
IL_0001: ldarg.1 // handle
IL_0002: ldnull
IL_0003: ceq
IL_0004: brfalse.s IL_0012 // transfer to line that stores into _handle
...
Of course, there would be more IL code generated with the second approach, but for the purpose of this question, I only typed the relevant part.
Questions:
1) Would there be any measurable difference between the two approaches?
The first approach (which is what the compiler does with my C# code above) calls brtrue/false)
directly on the object reference, whereas the second approach calls brtrue/false
on the result of comparison of the object with ldnull
(which is what the compiler generates with == null
checks).
From what I understand, the first approach processes at least one less instruction, so at some level, this is measurable.
2) If so, is there a way to force the compiler to generate such IL for null-checks in C#?
i.e. write an equivalent of myObj != null
in C#, where the generated IL calls brtrue.s
directly on myObj
reference.
Or am I missing something fundamental? IL isn't my strongest suit.
I am also open to someone reasonably convincing me that I'm just being crazy here :)
Thanks in advance!
The problem is that you are looking at a debug build. If you are worrying about micro optimizations, looking at a build with optimizations turned off is nonsensical.
In a release build, handle == null
will also make use of btrue.s
. On a debug build however, the generated IL will be as close as possible to the actual C# code in order to achieve a good debugging experience.
The following code:
public void M(object o) {
if (o == null)
throw new ArgumentException(); }
In Release mode will output the following IL:
IL_0000: ldarg.1
IL_0001: brtrue.s IL_0009
IL_0003: newobj instance void [mscorlib]System.ArgumentException::.ctor()
IL_0008: throw
IL_0009: ret