Article: Comparing Nullables

Home Page


Consultancy

  • Service Vouchers
  • Escrow Service

Shop



Programming
  • Articles
  • Tools
  • Links

Search

 

Contact

 

Chess Puzzles




DWHS

Valid XHTML 1.0 Transitional
Valid CSS!
Mobile-friendly!

Comparing Nullable values produces some counter-intuitive results, and its behavior is language-dependent.

category 'KB', language C# and VB.NET, created 13-Feb-2010, version V1.0, by Luc Pattyn


License: The author hereby grants you a worldwide, non-exclusive license to use and redistribute the files and the source code in the article in any way you see fit, provided you keep the copyright notice in place; when code modifications are applied, the notice must reflect that. The author retains copyright to the article, you may not republish or otherwise make available the article, in whole or in part, without the prior written consent of the author.

Disclaimer: This work is provided “as is”, without any express or implied warranties or conditions or guarantees. You, the user, assume all risk in its use. In no event will the author be liable to you on any legal theory for any special, incidental, consequential, punitive or exemplary damages arising out of this license or the use of the work or otherwise.


Since version 2.0 the .NET Framework offers nullable types; when a value type is nullable, it allows for a null value, which may come in handy as it can indicate the absence of an actual value. However, there are some strange things going on when comparing nullable values, and the exact behavior is language-dependent.

The test program

I created basically the same console application both in C# and in VB.NET; here is the C# code:

using System;

namespace Nullable1 {
  class Program {
    static void Main(string[] args) {
      int? x = null;
      int? y = null;
      Compare(x, y);
      x = 1;
      Compare(x, y);
      y = 2;
      Compare(x, y);
      x = null;
      Compare(x, y);
      Console.ReadLine();
    }

    private static void Compare(int? x, int? y) {
      if (x == y) Console.WriteLine("{0} == {1} is True", toString(x), toString(y));
      else Console.WriteLine("{0} == {1} is False", toString(x), toString(y));

      if (x != y) Console.WriteLine("{0} != {1} is True", toString(x), toString(y));
      else Console.WriteLine("{0} != {1} is False", toString(x), toString(y));

      if (x >= y) Console.WriteLine("{0} >= {1} is True", toString(x), toString(y));
      else Console.WriteLine("{0} >= {1} is False", toString(x), toString(y));

      if (x > y) Console.WriteLine("{0} > {1} is True", toString(x), toString(y));
      else Console.WriteLine("{0} > {1} is False", toString(x), toString(y));

      if (x < y) Console.WriteLine("{0} < {1} is True", toString(x), toString(y));
      else Console.WriteLine("{0} < {1} is False", toString(x), toString(y));

      if (x <= y) Console.WriteLine("{0} <= {1} is True", toString(x), toString(y));
      else Console.WriteLine("{0} <= {1} is False", toString(x), toString(y));

      Console.WriteLine("Nullable.Equals({0}, {1}) is {2}", toString(x), toString(y), Nullable.Equals(x, y));
      Console.WriteLine("Nullable.Compare({0}, {1}) is {2}", toString(x), toString(y), Nullable.Compare(x, y));
      Console.WriteLine();
    }

    private static string toString(int? x) {
      if (x.HasValue) return x.ToString();
      return "null";
    }
  }
}

And this is the VB.NET code that at first glance is equivalent (but later on we will see the results are different):

Module Nullable1
  Sub Main()
    Dim x As Nullable(Of Int32) = Nothing
    Dim y As Nullable(Of Int32) = Nothing
    Compare(x, y)
    x = 1
    Compare(x, y)
    y = 2
    Compare(x, y)
    x = Nothing
    Compare(x, y)
    Console.ReadLine()
  End Sub


  Private Sub Compare(ByVal x As Nullable(Of Int32), ByVal y As Nullable(Of Int32))
    If x = y Then
      Console.WriteLine("{0} = {1} is True", toString(x), toString(y))
    Else
      Console.WriteLine("{0} = {1} is False", toString(x), toString(y))
    End If
    If x <> y Then
      Console.WriteLine("{0} <> {1} is True", toString(x), toString(y))
    Else
      Console.WriteLine("{0} <> {1} is False", toString(x), toString(y))
    End If
    If x >= y Then
      Console.WriteLine("{0} >= {1} is True", toString(x), toString(y))
    Else
      Console.WriteLine("{0} >= {1} is False", toString(x), toString(y))
    End If
    If x > y Then
      Console.WriteLine("{0} > {1} is True", toString(x), toString(y))
    Else
      Console.WriteLine("{0} > {1} is False", toString(x), toString(y))
    End If
    If x < y Then
      Console.WriteLine("{0} < {1} is True", toString(x), toString(y))
    Else
      Console.WriteLine("{0} < {1} is False", toString(x), toString(y))
    End If
    If x <= y Then
      Console.WriteLine("{0} <= {1} is True", toString(x), toString(y))
    Else
      Console.WriteLine("{0} <= {1} is False", toString(x), toString(y))
    End If
    Console.WriteLine("Nullable.Equals({0}, {1}) is {2}", toString(x), toString(y), Nullable.Equals(x, y))
    Console.WriteLine("Nullable.Compare({0}, {1}) is {2}", toString(x), toString(y), Nullable.Compare(x, y))
    Console.WriteLine()
  End Sub

  Private Function toString(ByVal x As Nullable(Of Int32)) As String
    If x.HasValue Then Return x.ToString()
    Return "Nothing"
  End Function
End Module

The test results

And here is the output for each of those applications using Visual Studio 2008; targetting either .NET 2.0 or 3.5 returns the same results:

null == null is True
null != null is False
null >= null is False
null > null is False
null < null is False
null <= null is False
Nullable.Equals(null, null) is True
Nullable.Compare(null, null) is 0

1 == null is False
1 != null is True
1 >= null is False
1 > null is False
1 < null is False
1 <= null is False
Nullable.Equals(1, null) is False
Nullable.Compare(1, null) is 1

1 == 2 is False
1 != 2 is True
1 >= 2 is False
1 > 2 is False
1 < 2 is True
1 <= 2 is True
Nullable.Equals(1, 2) is False
Nullable.Compare(1, 2) is -1

null == 2 is False
null != 2 is True
null >= 2 is False
null > 2 is False
null < 2 is False
null <= 2 is False
Nullable.Equals(null, 2) is False
Nullable.Compare(null, 2) is -1

Nothing = Nothing is False
Nothing <> Nothing is False
Nothing >= Nothing is False
Nothing > Nothing is False
Nothing < Nothing is False
Nothing <= Nothing is False
Nullable.Equals(Nothing, Nothing) is True
Nullable.Compare(Nothing, Nothing) is 0

1 = Nothing is False
1 <> Nothing is False
1 >= Nothing is False
1 > Nothing is False
1 < Nothing is False
1 <= Nothing is False
Nullable.Equals(1, Nothing) is False
Nullable.Compare(1, Nothing) is 1

1 = 2 is False
1 <> 2 is True
1 >= 2 is False
1 > 2 is False
1 < 2 is True
1 <= 2 is True
Nullable.Equals(1, 2) is False
Nullable.Compare(1, 2) is -1

Nothing = 2 is False
Nothing <> 2 is False
Nothing >= 2 is False
Nothing > 2 is False
Nothing < 2 is False
Nothing <= 2 is False
Nullable.Equals(Nothing, 2) is False
Nullable.Compare(Nothing, 2) is -1

The results are normal for the Equals and Compare methods. The results are normal too for comparison operators when both operands are not null/Nothing; however when one or both operands are null or Nothing:

  1. the results depend on the language used: in VB.NET the six comparison operators return False, in C# four of them always return false, however == and != work in the intuitive way.
  2. the net result is operators that seem to be each other's inverse (such as < and >=) don't always produce opposite results, and the OR logic in <= and >= seems defective as they can return false even when == returns true!

Conclusion

Nullable comparisons hold a couple of surprises, the results may be counter-intuitive and they depend on the language used.

History

  • Version 1.0 (13-Feb-2010): Original version


Perceler

Copyright © 2012, Luc Pattyn

Last Modified 02-Sep-2013