C#2.0: Nullable types, lifted operators and some compare weirdness

Recently I was stumped on some weird comparisons and I promised myself I'd do some investigations. I figured out with help of some people like Erric Lippert they are due to the inclusion of nullable types in the language. When a new feature is added the ripple effects reach every nook and corner of the language, nullable types are no exception to this. Consider the following code and try to guess whether they'll compile

 int i = 5;if ( i == null )    Console.WriteLine ("null");if ( 5 == null )   Console.WriteLine ("null");

The answer is "NO" on C#1.1 but "YES, with warnings" on C#2.0. The warning give away the reason why they compile "The result of the expression is always 'false' since a value of type 'int' is never equal to 'null' of type 'int?'  ". The part in bold clearly indicates that the i and 5 are getting converted to the nullable type int?.

Lifted conversion operator

According to the C#2.0 spec if there exists an conversion from a type S to T then a lifted conversion operator exists from S? to T?. For this conversion T? will be unwrapped to T, then T will be converted to S and then S will be wrapped into a S?

 struct S{    public static implicit operator S(T t){        Console.WriteLine("Conversion T=>S");        return new S();    }}struct T{ }class Program{    static void Main(string[] args)    {         T t = new T();        S s = t;        T ? tq = newT (); S ? sq = tq;     }}

In the above code even if T? to S? conversion does not exist directly it will go through. The output will have two "Conversion S=>T" indicating that even for the second conversion the same conversion operator was called. There is an exception rule to it though. Consider the following when applied to the same classes

 T? tq  =  null;
S? sq = tq;

For this the compilation will go through as ususual but the convertion function will not be called and sq will be null. This is called null propagating in which the null source is directly converted into null target without going through the underlying conversion function. However, the conversion function needs to exist so that the compiler can verify that the conversion is valid (even though it does not call it). In our conversion function we create a new S irrespective of the value of T. So if our function was called, sq would have been non-null which is not the case...

Lifted Operators

Lifted operators allow predefined and user-defined operators (overloaded-operators) that work for non-nullable types to be used for their nullable forms as well. Consider the code below

 struct S{    public int val;    public S(int val)
    {        this.val = val;    }     public static S operator +(S s1, S s2)    {        Console.WriteLine("+ operator called");        return new S(s1.val + s2.val);    }} // user-defined operatorsS? sq1 = new S(5);S? sq2 = new S(6);S? resq = sq1 + sq2; // resq will be 11resq = sq1 + null;   // resq will be null  // pre-defined operatorsint i = 5;int? j = 6;int? k = j + i;    // 11int? q = i + null; // null

In the above code even though the + operator is overloaded for S it also works for S?. Even here null propagation takes place and for resq = sq1 + null; the overloaded operator function is not called as one of the sources is null.

For the expression S? resq = sq1 + sq2; the code that is generated is something like

 S? resq = (sq1.HasValue && sq2.HasValue) ? (sq1.Value + sq2.Value) : (S?)null;

Equality operator

This is the one I hit in my previous blog. The == and != operators have been lifted so that it allows one operand to be a value of a nullable type and the other to be the null literal. So both of the following expressions are valid

 int i = 5;if (i == null )      Console.WriteLine("null");if (5 == null )     Console.WriteLine("null");

However if you want to get the following to work you need overload the == and != (and Equals) of the type S

 S s = new S();// compile error is == and != is not overloadedif (s == null) Console.WriteLine("null");