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?
int? x = null;
if (x < 100) {
Console.WriteLine("True, {0} is less than 100.",
x.HasValue ? x.ToString() : "null");
} else {
Console.WriteLine("False, {0} is NOT less than 100.",
x.HasValue ? x.ToString() : "null");
}
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:
int? x = null;
if (x < int.MaxValue) {
// ...
}
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:
int? x = null;
// um... hold on here, x is NOT less than min value?
if (x < int.MinValue) {
// ...
}
So what’s going on here? If we use greater than instead of less than, we see the same little dilemma:
int? x = null;
// once again, null is not greater than anything either...
if (x > int.MinValue) {
// ...
}
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!
DateTime? x = null;
DateTime? y = null;
if (x <= y) {
Console.WriteLine(
"You'd think this is true, since both are null, but it's not.");
} else {
Console.WriteLine("It's false because <=, <, >, >= don't work on null.");
}
To make matters even more confusing, take for example your usual check to see if something is less than, greater to, or equal:
int? x = null;
int? y = 100;
if (x < y) {
Console.WriteLine("X is less than Y");
} else if (x > y) {
Console.WriteLine("X is greater than Y");
} else {
// We fall into the "equals" assumption, but clearly null != 100!
Console.WriteLine("X is equal to Y");
}
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:
int? x = null;
int? y = 100;
if (x == y) {
Console.WriteLine("False, x is null, y is not.");
}
if (x != y) {
Console.WriteLine("True, x is null, y is not.");
}
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:
if (x.HasValue) {
if (y.HasValue) {
if (x < y) {
Console.WriteLine("x < y");
} else if (x > y) {
Console.WriteLine("x > y");
} else {
Console.WriteLine("x == y");
}
} else {
Console.WriteLine("x > y because y is null and x isn't");
}
} else if (y.HasValue) {
Console.WriteLine("x < y because x is null and y isn't");
} else {
Console.WriteLine("x == y because both are null");
}
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:
int? x = null;
int? y = 100;
if (Nullable.Compare(x, y) < 0) {
// Yes! x is null, y is not, so x is less than y according to Compare().
Console.WriteLine("x < y");
} else if (Nullable.Compare(x, y) > 0) {
Console.WriteLine("x > y");
} else {
Console.WriteLine("x == y");
}
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.
Technorati Tags: C#,C-Sharp,.NET,Little Wonders,Little Pitfalls,Nulalble