IComparable in the new Nullable world

Our VP, Soma posted about the last minute changes to Nullable<T> in Visual Studio 2005. The changes have been available in August CTP for a while and comments have been coming in. Keep them coming.

Here is a thumbnail sketch of what the recent nullable changes do. I am really glad that we did those changes and as a program manager driving those changes at the last moment in the product cycle, I am glad that they are done and in your hands.

We wanted to unify reference nulls and value nulls (Nullable<T>) better. The main blocker was that in VS 2005 beta2, boxing a null value gave you a boxed Nullable<T>, not a null reference

  • Boxing operation unwraps the "Nullable wrapper" and gives you either a boxed T or a null ref. In turn, unboxing gives a T? or a T from a boxed T.
  • Interfaces implemented by T get "lifted" to T? (T? is the short hand syntax for Nullable<T> in C#). Likewise, "is" operator provides similar behavior - T? is IFoo if T is IFoo
  • Members don't get "lifted" from T to T? and T? does not satisfy the constraints that T does

Due to the last point, Peter Brown ran into an interesting issue. Earlier, Nullable<T> implemented IComparable so it could satisfy a generic constraint of the form. But with the latest changes, Nullable<T> no longer satisfies the IComparable constraint.

Here is the problem in a nutshell:

With the new Nullable<T> implementation, how can one write a generic method that can take two arguments of the same generic type – whether nullable or not and compare the arguments.

Let’s create a simple method signature for illustration

   // returns true if v1 < v2 and false otherwise
static bool IsLess<T>(T v1, T v2)

 

Here are some options:

1. Quick and dirty answer: Drop the IComparable constraints and cast to IComparable.

   return ((((IComparable)v1).CompareTo(v2) < 0) ? true
: false);

If you like, you can even check to make sure that if the argument is T?, then T implements IComparable

 

2. Write two separate methods – one for nullable and another for other value types. (More about the interesting problems with overload resolutions later)

 

But as one of my colleagues pointed out, there is an even better option – use the default comparer! It does the right thing – checks for IComparable / IComparable<T> implementation and otherwise provides a default comparer. So here is a brief sketch

  static bool IsLess3<T>(T v1, T v2)

    {

        Comparer<T> comparer =
System.Collections.Generic.Comparer<T>.Default;

        return (comparer.Compare(v1, v2) < 0 ? true
: false);

    }

As a bonus, the lookup is efficient because it is done only once and then cached. So subsequent calls don’t incur the lookup overhead.