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 would you 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 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 )
.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 a 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 (confusingly) 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 is rather 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;
}
}
}
Hope this helps!
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.