Sponsors

Testing References for Null-ness

Posted on 6/19/2007

Other programmers who read my code often ask, "Kevin, why do you cast references to the type System.Object before you test to see if they are null?" People who know me also know that I don’t do anything without a good reason. And there is a good reason to cast references to type Object before comparing for null-ness.

The problem is operator overloading in C#. To be fair, operator overloading is not the real problem. Bad programmers who don’t know how to write them are the problem. Operator overloading as a feature of the C# language is a very good thing. Used correctly, it can make your code a lot more expressive. Used incorrectly, it can be confusing and dangerous. Can you spot the bug in the following code?

static void Main( string[] args )
{
       BadClass bc = new BadClass( 5 );
       if (bc != null)
               Console.WriteLine( bc.Data );
}

That code looks fine but what you don’t know yet is that the BadClass overloads the inequality test operator (!=) in a very inappropriate way. Take a look at the class below.

public class BadClass
{
       private int m_data = -1;

       public int Data {
               get { return m_data; }
               set { m_data = value; }
       }

       public BadClass( int data ) {
               Data = data;
       }

       public static bool operator ==(
               BadClass L, BadClass R ) {
               return (L.Data == R.Data);
       }

       public static bool operator !=(
               BadClass L, BadClass R ) {
               return !(L == R);
       }
}

In this class, the inequality operator defers to the equality operator which dereferences the operands L and R without testing them for null-ness first. When you write code like Main above that passes null for one of those references, you will get a nasty NullReferenceException at runtime as a result. Some old-timers like me learned to trick certain C++ compilers into not calling the overloaded operator specifically when testing for null-ness by reversing the operands, putting the null first like this:

static void Main( string[] args )
{
       BadClass bc = new BadClass( 5 );
       if (null != bc)
               Console.WriteLine( bc.Data );
}

You see, for operators that are left-associative, i.e. they evaulate from left to right, some C++ compilers will only look for methods that overloaded operators as defined in the type of the left operand. This was because overloaded operators were very often defined as instance methods in C++, whereas C# requires all overloaded operators to be static. Therefore, most binary operations in C++ could be implemented with methods that accepted only one parameter for the right operand, using the so-called this parameter as the left operand. Using C++, simply specifying null first in an equality or inequality test meant that the compiler would not invoke any overloaded operator because null has no operators defined on it.

In C#, since all overloaded operators are static, both operands must be supplied when defining a binary operator. However, the C# compiler evaluates both arguments for an equality test (==) or an inequality test (!=) even though those operators are left-associative. So, if either argument matches a type known to implement a specified operator and if the other operand can be coerced into the same type, the C# compiler will invoke the method that defines the operation. It so happens that null can be coerced to any type so when the compiler sees if (null != whatever), it's no different than saying if (whatever != null). In fact, if you run the new Main method from above, you will see that the trick we used in C++ to coerce the compiler into skipping the call to the overloaded operator doesn’t work in C#. There is a way to replicate the C++ trick in C# though. Look at this third version of the Main method if you can figure out why it works:

static void Main( string[] args )
{
       BadClass bc = new BadClass( 5 );
       if ((object)bc != null)
               Console.WriteLine( bc.Data );
}

Of course, this simple cast operation causes the compiler to evaluate the BadClass reference as a System.Object type which has no overloaded inequality test operator defined in its method table. So the code that gets generated does a simple reference comparison instead.

Some of you may use the Object.ReferenceEquals method to do the same thing. However, using ReferenceEquals inserts a method call into the MSIL where as the cast and compare noted above does not. That can mean a difference in performance if you are iterating over a large list and comparing object references to null before using them. For that reason, I recommend using the cast and compare method instead. I will even go so far as to say that if your classes have properly implemented equality and inequality test operators defined on them, you should still cast references to them to type Object before comparing them to null. Why incur the overhead of invoking your overloaded operators for this special case? It will only cost you in terms of performance and buys nothing in return. And once you're in the habit of doing this, you will be protecting yourself from bad programmers who don't know how to handle null operands in the overloaded operators they write.

Comments

POST A COMMENT
Your name:
Your e-mail address (will not be shared with others):
Your comment: