Search code examples
c#.netcilopcode

Is there C# code to invoke the null-check feature of `brtrue`/`brfalse` instruction directly on an object reference?


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!


Solution

  • 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