James Michael Hare

...hare-brained ideas from the realm of software development...
posts - 137 , comments - 1106 , trackbacks - 0

My Links

News

Welcome to my blog! I'm a Sr. Software Development Engineer in Seattle, WA. I've been doing C++/C#/Java development for over 18 years, but have definitely learned that there is always more to learn!

All thoughts and opinions expressed in my blog and my comments are my own and do not represent the thoughts of my employer.

Blogs I Read

MCC Logo MVP Logo

Follow BlkRabbitCoder on Twitter

Tag Cloud

Archives

Post Categories

C#/.NET Little Wonders: The Nullable static class

Once again, in this series of posts I look at the parts of the .NET Framework that may seem trivial, but can help improve your code by making it easier to write and maintain.

The index of all my past little wonders posts can be found here.

Today we’re going to look at an interesting Little Wonder that can be used to mitigate what could be considered a Little Pitfall.  The Little Wonder we’ll be examining is the System.Nullable static class.

No, not the System.Nullable<T> class, but a static helper class that has one useful method in particular that we will examine… but first, let’s look at the Little Pitfall that makes this wonder useful.

But first, let’s set the stage with an example comparing null values to non-null values…

Little Pitfall: Comparing nullable value types using <, >, <=, >=

Examine this piece of code, without examining it too deeply, what’s your gut reaction as to the result? 

   1: int? x = null;
   2:  
   3: if (x < 100)
   4: {
   5:     Console.WriteLine("True, {0} is less than 100.",
   6:                       x.HasValue ? x.ToString() : "null");
   7: }
   8: else
   9: {
  10:     Console.WriteLine("False, {0} is NOT less than 100.",
  11:                       x.HasValue ? x.ToString() : "null");
  12: }

Your gut might be to say either true or a NullReferenceException right?  It would seem to make sense that either you’d throw because one of the values was null, or that a null integer is less than the integer constant 100.  But the result is actually false!  The null value is not less than 100 according to the less-than operator.

It looks even more confusing when you consider this also evaluates to false:

   1: int? x = null;
   2:  
   3: if (x < int.MaxValue)
   4: {
   5:     // ...
   6: }

So, are we saying that null is less than every valid int value?  If that were true, null should be less than int.MinValue, right?  Well… no:

   1: int? x = null;
   2:  
   3: // um... hold on here, x is NOT less than min value?
   4: if (x < int.MinValue)
   5: {
   6:     // ...
   7: }

So what’s going on here?  If we use greater than instead of less than, we see the same little dilemma:

   1: int? x = null;
   2:  
   3: // once again, null is not greater than anything either...
   4: if (x > int.MinValue)
   5: {
   6:     // ...
   7: }

It turns out that four of the comparison operators (<, <=, >, >=) are designed to return false anytime at least one of the arguments is null when comparing System.Nullable wrapped types that expose the comparison operators (short, int, float, double, DateTime, TimeSpan, etc.).

This is actually by design because null as a Nullable<T> value for the purposes of a comparison is undefined. Is null a very high value? is it a very low value? Well, when it comes to the comparison operators, .NET considers it to be neither high nor low, so the <, <=, >, >= operators will all return false.  This can be a point of confusion for novice developers who might have expected a NullReferenceException or some notion of null being a very low value.

What’s even odder is that even though the two equality operators (== and !=) work as expected, >= and <= have the same issue as < and > and return false if both System.Nullable wrapped operator comparable types are null!

   1: DateTime? x = null;
   2: DateTime? y = null;
   3:  
   4: if (x <= y)
   5: {
   6:     Console.WriteLine("You'd think this is true, since both are null, but it's not.");
   7: }
   8: else
   9: {
  10:     Console.WriteLine("It's false because <=, <, >, >= don't work on null.");
  11: }

To make matters even more confusing, take for example your usual check to see if something is less than, greater to, or equal:

   1: int? x = null;
   2: int? y = 100;
   3:  
   4: if (x < y)
   5: {
   6:     Console.WriteLine("X is less than Y");
   7: }
   8: else if (x > y)
   9: {
  10:     Console.WriteLine("X is greater than Y");
  11: }
  12: else
  13: {
  14:     // We fall into the "equals" assumption, but clearly null != 100!
  15:     Console.WriteLine("X is equal to Y");
  16: }

Yes, this code outputs “X is equal to Y” because both the less-than and greater-than operators return false when a Nullable wrapped operator comparable type is null.  This violates a lot of our assumptions because we assume is something is not less than something, and it’s not greater than something, it must be equal. But since comparing Nullable<T> wrapped types is undefined, this is not the case and our logic tree breaks down.

So keep in mind, that the only two comparison operators that are defined to operate on Nullable wrapped types where at least one is null are the equals (==) and not equals (!=) operators:

   1: int? x = null;
   2: int? y = 100;
   3:  
   4: if (x == y)
   5: {
   6:     Console.WriteLine("False, x is null, y is not.");
   7: }
   8:  
   9: if (x != y)
  10: {
  11:     Console.WriteLine("True, x is null, y is not.");
  12: }

Solution: The Nullable static class

So we’ve seen that <, <=, >, and >= have some interesting and perhaps unexpected behaviors that can trip up a novice developer who isn’t expecting the “undefined” behavior that System.Nullable<T> types with comparison operators can cause.  But what if we do want to consider null to be a very low value?  For example, what if we are sorting a list of values to be displayed in a list, and one of those items is a current stock price, and we want any items without a current stock price to be grouped together as a value lower than any valid value:

Symbol      Description                Last Price
------      -----------------------    ----------
ABCZ        Apples and Oranges Inc     n/a
XYZP        Zippers and Buttons Inc    1.57
AZAZ        Carrots and Turnips Inc    23.13

 

The comparison operators won’t work against a null, so if we want to compare for the purposes of a Sort(), we could do some very complex logic such as:

   1: if (x.HasValue)
   2: {
   3:     if (y.HasValue)
   4:     {
   5:         if (x < y)
   6:         {
   7:             Console.WriteLine("x < y");
   8:         }
   9:         else if (x > y)
  10:         {
  11:             Console.WriteLine("x > y");
  12:         }
  13:         else
  14:         {
  15:             Console.WriteLine("x == y");
  16:         }
  17:     }
  18:     else
  19:     {
  20:         Console.WriteLine("x > y because y is null and x isn't");
  21:     }
  22: }
  23: else if (y.HasValue)
  24: {
  25:     Console.WriteLine("x < y because x is null and y isn't");
  26: }
  27: else
  28: {
  29:     Console.WriteLine("x == y because both are null");
  30: }

Yes, we could probably simplify this logic a bit, but it’s still rather messy!  But there’s a cleaner way to do this, and its all built into the System.Nullable static class.  This class is a companion class to the System.Nullable<T> class and allows you to use a few helper methods for Nullable<T> wrapped types, including a static Compare<T>() method of the.

What’s so big about the static Compare<T>() method?  It implements an IComparer compatible comparison on Nullable<T> types.  Why do we care?  Well, if you look at the MSDN description for how IComparer works, you’ll read:

Comparing null with any type is allowed and does not generate an exception when using IComparable. When sorting, null is considered to be less than any other object.

So, if you want to sort items and consider null to be less than any value, this will work!  So now we can change our logic to use the Nullable.Compare<T>() static method:

   1: int? x = null;
   2: int? y = 100;
   3:  
   4: if (Nullable.Compare(x, y) < 0)
   5: {
   6:     // Yes!  x is null, y is not, so x is less than y according to Compare().
   7:     Console.WriteLine("x < y");
   8: }
   9: else if (Nullable.Compare(x, y) > 0)
  10: {
  11:     Console.WriteLine("x > y");
  12: }
  13: else
  14: {
  15:     Console.WriteLine("x == y");
  16: }

 

Summary

So, when doing math comparisons between two numeric values where one of them may be a null Nullable<T>, consider using the System.Nullable.Compare<T>() method instead of the comparison operators.  It will treat null less than any value, and will avoid logic consistency problems when relying on < returning false to indicate >= is true and so on.

 

Print | posted on Thursday, June 30, 2011 6:52 PM | Filed Under [ My Blog C# Software .NET Little Wonders Little Pitfalls ]

Feedback

Gravatar

# re: C#/.NET Little Wonders: The Nullable static class

-- this is evil! if you don't -want- null values in your variable, don't use a nullable type! NULL is not a number and so doesn't compare with comparison operators. If anyone else uses the code they won't have any idea that you've adopted a convention where NULL is negative-infinity. Why not POSITIVE infinity?? it's like overloading the caret operator in C++ to be cross-product. Cunning, but how's anyone ever supposed to know that?
6/30/2011 7:35 PM | podfish
Gravatar

# re: C#/.NET Little Wonders: The Nullable static class

@podfish: It isn't quite intuitive, granted. I'd almost wish they hadn't allowed Nullables to be compared directly.
6/30/2011 10:21 PM | James Michael Hare
Gravatar

# re: C#/.NET Little Wonders: The Nullable static class

When reading your first example, i found myself auto expanding it to :
if ( x.HasValue() && x < 100 )
So as long as you are expecting it, i don't think its too bad. The only catch would be is if you are not expecting x to be nullable. Possibly variable naming convention would solve that one.
7/1/2011 5:29 AM | Andy
Gravatar

# re: C#/.NET Little Wonders: The Nullable static class

@Andy: Very true, as long as you know what's going on, it's reasonable. However, if someone don't realize the underlying type is nullable<int> instead of int (for example from a web service proxy), it can catch someone by surprise.
7/1/2011 8:35 AM | James Michael Hare
Post A Comment
Title:
Name:
Email:
Comment:
Verification:
 

Powered by: