Wednesday, January 04, 2017

C# under the covers III

So, you have some code that looks like this


    private static bool Match(int item, int? target)
    {
        if (target.HasValue)
        {
            return item == target;
        }

        return false;
    }

How many tests do you need to write to get 100% branch coverage?

If you answered "two -- one with a value, one without", you'd be as surprised as I was when I tried it.

It turns out that the implicit extraction of the value of the nullable value contains its own HasValue check, and the IL looks like


IL_0000: nop
IL_0001: ldarga.s target
IL_0003: call instance bool valuetype [mscorlib]System.Nullable\u00601::get_HasValue()
IL_0008: ldc.i4.0
IL_0009: ceq
IL_000b: stloc.1
IL_000c: ldloc.1
IL_000d: brtrue.s IL_002b

IL_000f: nop
IL_0010: ldarg.0
IL_0011: stloc.2
IL_0012: ldarg.1
IL_0013: stloc.3
IL_0014: ldloc.2
IL_0015: ldloca.s CS$0$0003
IL_0017: call instance !0 valuetype [mscorlib]System.Nullable\u00601::GetValueOrDefault()
IL_001c: bne.un.s IL_0027

IL_001e: ldloca.s CS$0$0003
IL_0020: call instance bool valuetype [mscorlib]System.Nullable\u00601::get_HasValue()
IL_0025: br.s IL_0028

IL_0027: ldc.i4.0

IL_0028: stloc.0
IL_0029: br.s IL_002f

IL_002b: ldc.i4.0
IL_002c: stloc.0
IL_002d: br.s IL_002f

IL_002f: ldloc.0
IL_0030: ret

Roughly, it goes "get a value, or the default, and test that; if not equal, accept that, otherwise only accept the equality if the nullable had a value."

If you write


    private static bool Match(int item, int? target)
    {
        if (target.HasValue)
        {
            return item == target.Value;
        }

        return false;
    }

then there is no compiler-generated branch for you to be caught by -- and it's probably slightly better coding practise, anyway.


Post a Comment