.NET

Nullable Types’ Subtlety

I did a post called .Net Nullable Types on GeekQuiz.Net that I think is worth sharing here (in case you are not following me there). Also FunnelWeb has a nicer and more readable format. So here we go.

In the following code snippet the nullable.HasValue condition is not necessary. Why?

public static int ToInt(this object value)
{
    var result = int.MinValue;
    if (value is int?)
    {
        var nullable = ((int?)value);
        if (nullable.HasValue) 
            result = nullable.Value;
    }
    return result;
}

Also how you would implement this method?

The short answer

According to msdnAn ‘is’ expression evaluates to true if the provided expression is non-null‘. So when the ‘is‘ condition is fulfilled the value is not null; i.e. nullable.HasValue is always true.

The long answer

To understand the the behavior of ‘is‘ operator I am going to need to dig rather deep. So bear with me:

Below is the IL code for ToInt (to get the IL code you may run ildasm from VS command prompt and open your executable there):

.method public hidebysig static int32  ToInt(object 'value') cil managed
{
  .custom instance void [System.Core]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 )
  // Code size       60 (0x3c)
  .maxstack  2
  .locals init ([0] int32 result,
           [1] valuetype [mscorlib]System.Nullable`1<int32> nullable,
           [2] int32 CS$1$0000,
           [3] bool CS$4$0001)
  IL_0000:  nop
  IL_0001:  ldc.i4     0x80000000
  IL_0006:  stloc.0
  IL_0007:  ldarg.0
  IL_0008:  isinst     valuetype [mscorlib]System.Nullable`1<int32>
  IL_000d:  ldnull
  IL_000e:  cgt.un
  IL_0010:  ldc.i4.0
  IL_0011:  ceq
  IL_0013:  stloc.3
  IL_0014:  ldloc.3
  IL_0015:  brtrue.s   IL_0036
  IL_0017:  nop
  IL_0018:  ldarg.0
  IL_0019:  unbox.any  valuetype [mscorlib]System.Nullable`1<int32>
  IL_001e:  stloc.1
  IL_001f:  ldloca.s   nullable
  IL_0021:  call       instance bool valuetype [mscorlib]System.Nullable`1<int32>::get_HasValue()
  IL_0026:  ldc.i4.0
  IL_0027:  ceq
  IL_0029:  stloc.3
  IL_002a:  ldloc.3
  IL_002b:  brtrue.s   IL_0035
  IL_002d:  ldloca.s   nullable
  IL_002f:  call       instance !0 valuetype [mscorlib]System.Nullable`1<int32>::get_Value()
  IL_0034:  stloc.0
  IL_0035:  nop
  IL_0036:  ldloc.0
  IL_0037:  stloc.2
  IL_0038:  br.s       IL_003a
  IL_003a:  ldloc.2
  IL_003b:  ret
}

The bit I am interested in is the ‘is‘ operator which translates to ‘isinst‘ in IL_0008:

IL_0008:  isinst     valuetype [mscorlib]System.Nullable`1<int32>

Let’s have a look at ‘isinst‘ from CIL Instruction Set document. The specification uses ‘isinst typeTok‘ as the format of the instruction and below is description from the spec (emphasis mine):

typeTok is a metadata token (a typeref, typedef or typespec), indicating the desired class. If typeTok is a non-nullable value type or a generic parameter type it is interpreted as ‘boxed’ typeTok.If typeTok is a nullable type, Nullable<T>, it is interpreted as ‘boxed’ T.

The last bit is interesting; so the result of isinst depends on how boxing works for Nullable types. Well, let’s have a look at boxing then. First a little bit of code to see how the ToInt method is typically called:

int? input = 12;
Console.WriteLine(input.ToInt());

You would typically have a nullable int that you pass to ToInt (you may also pass other types in; but that is not very important in our case). Let’s have a look at the a bit of IL for the above snippet:

.locals init ([0] valuetype [mscorlib]System.Nullable`1<int32> input)
IL_0000:  nop
IL_0001:  ldloca.s   input
IL_0003:  ldc.i4.s   12
IL_0005:  call       instance void valuetype [mscorlib]System.Nullable`1<int32>::.ctor(!0)
IL_000a:  nop
IL_000b:  ldloc.0
IL_000c:  box        valuetype [mscorlib]System.Nullable`1<int32>
IL_0011:  call       int32 NullableParameter.IntExtensions::ToInt(object)
IL_0016:  pop

As expected, we are calling ‘box‘ IL instruction (at IL_000c) on the input variable. Let’s have a look at ‘box‘ instruction from CIL specification. The format is ‘box typeTok‘ (emphasis mine):

If typeTok is a value type, the box instruction converts val to its boxed form. When typeTok is a non-nullable type (§I.8.2.4), this is done by creating a new object and copying the data from val into the newly allocated object. If it is a nullable type, this is done by inspecting val’s HasValue property; if it is false, a null reference is pushed onto the stack; otherwise, the result of boxing val’s Value property is pushed onto the stack.’

Alternative implementations of ToInt method

So for nullable types, after boxing, which is the case for object input parameter, we either get null or an int value! So we could actually write the ToInt method as:

public static int ToInt2(this object value)
{
    var result = int.MinValue;
    if (value is int)
        result = (int)value;
    return result;
}

… and it would work perfectly with the code below:

int? input = 12;
Console.WriteLine(input.ToInt());
Console.WriteLine('test'.ToInt());
input = null;
Console.WriteLine(input.ToInt());

… but that could be a bit confusing because we are passing int? inside. The less confusing and more readable version is:

public static int ToInt3(this object value)
{
    var result = value as int?;
    return result ?? int.MinValue;
}

If value is int, you will get the int value; otherwise you will get int.MinValue.

That is it. And here is the complete code if you would like to give it a go. Please note that there are three versions of the method and only the first one is called from main:

using System;
namespace NullableParameter
{
    class Program
    {
        static void Main(string[] args)
        {
            int? input = 12;
            Console.WriteLine(input.ToInt());
            Console.WriteLine('test'.ToInt());
            input = null;
            Console.WriteLine(input.ToInt());
            Console.ReadLine();
        }
    }
    static class IntExtensions
    {
        public static int ToInt(this object value)
        {
            var result = int.MinValue;
            if (value is int?)
            {
                var nullable = ((int?)value);
                if (nullable.HasValue) 
                    result = nullable.Value;
            }
            return result;
        }
        public static int ToInt2(this object value)
        {
            var result = int.MinValue;
            if (value is int)
                result = (int)value;
            return result;
        }
        public static int ToInt3(this object value)
        {
            var result = value as int?;
            return result ?? int.MinValue;
        }
    }
}

If you liked this article there is a good chance you will like other articles/quizzes at geekquiz.net: another blog that I maintain in which I try to publish geeky quizzes for coding enthusiasts.

Reference: Nullable Types’ Subtlety from our NCG partner Mehdi Khalili at the Mehdi Khalili’s Blog blog.

Related Articles

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button