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 msdn “An ‘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.
Hope this helps.