Tuesday 13 December 2011

"is" operator confusion

A while back I stumbled upon the following code.
public void SomeMethod(object param)
        {            
            if(!(param is DateTime?))
            {
                return;
            }
            var d = (DateTime?) param;
            
            if(d.HasValue)
            {
                //do some stuff
            }
        }
Apart from the fact that you shouldn't pas an object to a method when you actualy want it to be a "DateTime?" type (code comes from a wpf converter where the parameter is passed as an object) and that an "as" operator would make a cleaner solution the thing that triggered my interest were the Resharper squigels on the line:

if(d.HasValue) 

It told me "Expression is always true".
How can this always be true? The only thing I know for sure is that the reference has a nullable datetime once I get to that part of the code. But what makes it think it could not be a null value?

As is often the case the answer was simpler than the question... The "is"-operator checks for type but also returns false when the value is null. It doesn't seem logical to me but on the other hand maybe it's a way of forcing you to use the "as"-operator when it comes to reference types.

And indeed when looking at the IL I recieve the following code.

IL_0000: nop
IL_0001: ldarg.0
IL_0002: isinst valuetype [mscorlib]System.Nullable`1
IL_0007: ldnull
IL_0008: cgt.un
IL_000a: stloc.1
IL_000b: ldloc.1
IL_000c: brtrue.s IL_0011
IL_000e: nop
IL_000f: br.s IL_0028
IL_0011: ldarg.0
IL_0012: unbox.any valuetype [mscorlib]System.Nullable`1
IL_0017: stloc.0
IL_0018: ldloca.s d
IL_001a: call instance bool valuetype [mscorlib]System.Nullable`1::get_HasValue()
IL_001f: ldc.i4.0
IL_0020: ceq
IL_0022: stloc.1
IL_0023: ldloc.1
IL_0024: brtrue.s IL_0028
IL_0026: nop
IL_0027: nop
IL_0028: ret


So starting from the top and supposing null is passed as an argument
IL_0001: ldarg.0
the parameter with value null is loaded on the stack
IL_0002: isinst valuetype [mscorlib]System.Nullable`1
the value null is popped from the stack and checked if it is an instance of DateTime? which is not the case so null is pushed on the stack
IL_0007: ldnull
null value is loaded on the stack
IL_0008: cgt.un
the stack is popped twice, in our case meaning null is popped twice from the stack. The null values are compared and as they have the same value 0 is pushed to the stak
IL_000a: stloc.1
pops the value 0 from the stack into variable 1
IL_000b: ldloc.1
pushes the value 0 in variable 1 on to the stack
IL_000c
pops value 0 from the stack and goes to IL_0011 (to continue the program flow) if the value is true which is not the case
IL_000f
goes to IL_0028 and the method is exited

So this brings us back to our C# code and explains why we know for sure that, once we get past the "is"-check,  the param is different from null so HasValue always results in true. Nice job Resharper.

A genuine case of RTFM I suppose. Where in this case M stands for MSDN..

2 comments:

  1. Not quite what I expected from my first real post but everybody's gotta learn someday. Now to pick up the pace.

    ReplyDelete
  2. Didn't know it checked for null, good thing 'as' is a best practice, and we all stick to best practices :)

    ReplyDelete