Jan 22, 2010

Nullable notes

Nullable is value type object. But the following code can be compiled.

int? x = null

Isn't "null" supposed to be used with reference type, why it can be assigned with value type? It turns out to be just syntax suger, and the compiler emit the following code. It does not call any constructor.


IL_0001:  ldloca.s   x
IL_0003:  initobj    valuetype [mscorlib]System.Nullable`1<int32>

However, if you write the following code, compiler will emit the msil like the following. It call the constructor.


int? y = 123;

IL_0009:  ldloca.s   y
IL_000b:  ldc.i4.s   123
IL_000d:  call       instance void valuetype [mscorlib]System.Nullable`1<int32>::.ctor(!0)

//called
public Nullable(T value) {
    this.value = value; 
    this.hasValue = true;
} 

Nullable has two implicit converter that help you to write the following code.


int? y = 246; //implict conversion that create a Nullable on the fly, using the following implicit operator
public static implicit operator Nullable<T>(T value) { 
    return new Nullable<T>(value);
}

int z = (int)y; //explict conversion using the following explicit operator, this is not cast operation, this may throw exception, if Nullable.HasValue is false
public static explicit operator T(Nullable<T> value) { 
    return value.Value;
} 

public T Value {
    get { 
        if (!HasValue) { 
            ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_NoValue);
        } 
        return value;
    }
}


You may wonder why we don't can write the following code?


int z = y; //error, you can not do this, because there is not implicit conversion

This is because we don't have implict converter to convert a Nullable<T> to T. If we have had the following operator, we will be able to write the code above.


public static implicit operator T(Nullable<T> value) { 
   if (!HasValue) 
   { 
       return value.Value; 
   }
   else
   {
       return default(T);
   }
} 

But Why we have explicit converter but not implicit converter. If we have had this operator, there will be no difference in using Nullable<T> and T. The purpose of Nullable<T> is to use value type T like a reference type. That is why in the Value property will throw exception if there is not value, we want to using a value type like a reference type!!! See the following example.

int? x = null;
    if (x == null)//msil will be like if (x.HasValue)
    {
       Console.WriteLine("x is null");
    }

Although we can not implicitly convert Nullable<T> to T, but C# compiler and CLR, allow us use Nullable<T> like T in most of case. So the following code is legal.


int? y = 0;
y++;

//compiler will emit the following code
//if (y.HasValue)
//{
//    int temp = y.Value;
//    temp++;
//    y = temp;
//}

Int32? x = 5;
Console.WriteLine (x.GetType()); // it is "System.Int32"; not "System.Nullable<int32>"

Int32? n = 5;
Int32 result = ((IComparable) n).CompareTo(5); // Compiles & runs OK
Console.WriteLine(result); // 0

/*
If the CLR didn't provide this special support, it would be more cumbersome for you to write code to call an interface method on a nullable value type. You'd have to cast the unboxed value type first before casting to the interface to make the call: */

Int32 result = ((IComparable) (Int32) n).CompareTo(5); // Cumbersome

Null-Coalescing ?? operator works with reference type.


string s = null
//
string s2 = s ?? "something";
// this line is be compiled to 
  IL_0003:  ldloc.0
  IL_0004:  dup
  IL_0005:  brtrue.s   IL_000d
  IL_0007:  pop
  IL_0008:  ldstr      "something"
  IL_000d:  stloc.1

But c# compiler, make "??" works for Nullable as well. But underneath the emitted code is completely different, like the following


int z = y ?? 100;

//it is equivalent as 
//z = (y.HasValue) ? y.Value : 100

//it is also equivalent as 
//z = y.GetValueOrDefault(100);

To sum this up, the purpose of Nullable type is to let a value type has a null value, but compiler also let us use it as value type as well.