Equals(item, null) or item == null
Asked Answered
H

7

50

Is code that uses the static Object.Equals to check for null more robust than code that uses the == operator or regular Object.Equals? Aren't the latter two vulnerable to being overridden in such a way that checking for null doesn't work as expected (e.g. returning false when the compared value is null)?

In other words, is this:

if (Equals(item, null)) { /* Do Something */ }

more robust than this:

if (item == null) { /* Do Something */ }

I personally find the latter syntax easier to read. Should it be avoided when writing code that will handle objects outside the author's control (e.g. libraries)? Should it always be avoided (when checking for null)? Is this just hair-splitting?

Hydrocarbon answered 17/8, 2010 at 22:11 Comment(2)
Another option is the is operator: if (item is null), available from C# 7+. See: What is the difference between "x is null" and "x == null"?Sociality
Also related: Checking if an object is null in C#.Sociality
A
88

There's no simple answer for this question. Anyone who says always use one or the other is giving you poor advice, in my opinion.

There are actually several different methods you can call to compare object instances. Given two object instances a and b, you could write:

  • Object.Equals(a,b)
  • Object.ReferenceEquals(a,b)
  • a.Equals(b)
  • a == b

These could all do different things!

Object.Equals(a,b) will (by default) perform reference equality comparison on reference types and bitwise comparison on value types. From the MSDN documentation:

The default implementation of Equals supports reference equality for reference types, and bitwise equality for value types. Reference equality means the object references that are compared refer to the same object. Bitwise equality means the objects that are compared have the same binary representation.

Note that a derived type might override the Equals method to implement value equality. Value equality means the compared objects have the same value but different binary representations.

Note the last paragraph above ... we'll discuss this a bit later.

Object.ReferenceEquals(a,b) performs reference equality comparison only. If the types passed are boxed value types, the result is always false.

a.Equals(b) calls the virtual instance method of Object, which the type of a could override to do anything it wants. The call is performed using virtual dispatch, so the code that runs depends on the runtime type of a.

a == b invokes the static overloaded operator of the **compile-time type* of a. If the implementation of that operator invokes instance methods on either a or b, it may also depend on the runtime types of the parameters. Since the dispatch is based on the types in the expression, the following may yield different results:

Frog aFrog = new Frog();
Frog bFrog = new Frog();
Animal aAnimal = aFrog;
Animal bAnimal = bFrog;
// not necessarily equal...
bool areEqualFrogs = aFrog == bFrog;
bool areEqualAnimals = aAnimal = bAnimal;

So, yes, there is vulnerability for check for nulls using operator ==. In practice, most types do not overload == - but there's never a guarantee.

The instance method Equals() is no better here. While the default implementation performs reference/bitwise equality checks, it is possible for a type to override the Equals() member method, in which case this implementation will be called. A user supplied implementation could return whatever it wants, even when comparing to null.

But what about the static version of Object.Equals() you ask? Can this end up running user code? Well, it turns out that the answer is YES. The implementation of Object.Equals(a,b) expands to something along the lines of:

((object)a == (object)b) || (a != null && b != null && a.Equals(b))

You can try this for yourself:

class Foo {
    public override bool Equals(object obj) { return true; }  }

var a = new Foo();
var b = new Foo();
Console.WriteLine( Object.Equals(a,b) );  // outputs "True!"

As a consequence, it's possible for the statement: Object.Equals(a,b) to run user code when neither of the types in the call are null. Note that Object.Equals(a,b) does not call the instance version of Equals() when either of the arguments is null.

In short, the kind of comparison behavior you get can vary significantly, depending on which method you choose to call. One comment here, however: Microsoft doesn't officially document the internal behavior of Object.Equals(a,b). If you need an iron clad gaurantee of comparing a reference to null without any other code running, you want Object.ReferenceEquals():

Object.ReferenceEquals(item, null);

This method makes the intent extremently clear - you are specifically expecting the result to be the comparison of two references for reference equality. The benefit here over using something like Object.Equals(a,null), is that it's less likely that someone will come along later and say:

"Hey, this is awkward, let's replace it with: a.Equals(null) or a == null

which potentially may be different.

Let's inject some pragmatism here, however. So far we've talked about the potential for different modalities of comparison to yield different results. While this is certainly the case, there are certain types where it's safe to write a == null. Built-in .NET classes like String and Nullable<T> have well defined semantics for comparison. Furthermore, they are sealed - preventing any change to their behavior through inheritance. The following is quite common (and correct):

string s = ...
if( s == null ) { ... }

It's unnecessary (and ugly) to write:

if( ReferenceEquals(s,null) ) { ... }

So in certain limited cases, using == is safe, and appropriate.

Allcot answered 17/8, 2010 at 22:29 Comment(7)
I thought my answer (Microsoft's answer by proxy) is a pretty simple answer.Survive
Questions like: "should I always/never do X" imply a knowledge gap about the nuances of the subject in question. I felt that a bit more detail would be helpful here to clarify why I don't think a simple answer is meaningful.Allcot
The static object.Equals(a,b) method can, in fact, call the overloaded/virtual Equals method on object a. If I recall correctly, it does something like ((object)a == (object)b) || (a != null && b != null && a.Equals(b)). (This is irrelevant when comparing to null, which is what the OP asks about, but is relevant to the general case.)Macronucleus
@LukeH: I added some narrative to talk about this case. I was originally focusing on just the instace-to-null comparison, but it's worth explaining this as well. Thanks for the feedback.Allcot
Very useful answer. However it muddies the difference between ReferenceEquals and Object.Equals, when discussing strings (and any other type that internally is a reference, but logically acts as a value). Saying "It's unnecessary and ugly to write ReferenceEquals(myString, null" misses the fact that it is just plain wrong to do so.Timberwork
IMHO, IN PRACTICE you should use "a == b", unless you KNOW you want reference comparison. If a type does not support "==" operator, then use Object.Equals(a, b). I recommend NEVER using "a.Equals(b)" because that causes exception if a is null. If a type were to be implemented that failed to handle Object.Equals correctly, that would be a bug in that implementation, not a bug in your use of it. Don't go to contortions trying to avoid the possibility that someone wrote a bad class.Timberwork
It's interesting that Visual Studio suggests converting ReferenceEquals(foo,null) to "foo is null"Wo
P
6

if (Equals(item, null)) is no more robust than if (item == null), and I find it more confusing to boot.

Parody answered 17/8, 2010 at 22:13 Comment(2)
It's possible for someone to overload the == operator without overriding Equals. This would be a bad design, but it's entirely possible for the semantics of equality comparison to differ between the two. Also, if the == operator is overloaded, it may do something entirely difference from the built-in Objects.Equals() method - which I believe just checks for reference equality.Allcot
@Allcot If you are that paranoid about the objects your checking, won't (null == obj) guarantee a reference compare? And even if == is overloaded, by definition the result will be the intended behavior. If it wasn't the intended behavior, that is a problem well outside the scope of this question.Threesome
T
5

When you want to test IDENTITY (same location in memory):

ReferenceEquals(a, b)

Handles nulls. And is not overridable. 100% safe.

But make sure you really do want IDENTITY test. Consider the following:

ReferenceEquals(new String("abc"), new String("abc"))

which returns false. In contrast:

Object.Equals(new String("abc"), new String("abc"))

and

(new String("abc")) == (new String("abc"))

both return true.

If you are expecting an answer of true in this situation, then you want an EQUALITY test, not an IDENTITY test. See the next part.


When you want to test EQUALITY (same contents):

  • Use "a == b" if the compiler doesn't complain.

  • If that is rejected (if the type of variable a does not define "==" operator), then use "Object.Equals(a, b)".

  • IF you are inside of logic where a is known to not be null, THEN you may use the more readable "a.Equals(b)". For example, "this.Equals(b)" is safe. Or if "a" is a field that is initialized at construction time, and the constructor throws exception if null is passed in as the value to be used in that field.

NOW, to address the original question:

Q: Are these susceptible to being overridden in some class, with code that does not handle null correctly, resulting in an exception?

A: Yes. The only way to get 100% safe EQUALITY test would be to pre-test for nulls yourself.

But should you? The bug would be in that (hypothetical future bad class), and it would be a straightforward type of failure. Easy to debug and fix (by whoever supplies the class). I doubt it is a problem that happens often, or persists long when it does happen.

More detailed A: Object.Equals(a, b) is most likely to work in the face of a poorly written class. If "a" is null, the Object class will handle it itself, so no risk there. If "b" is null, then the DYNAMIC (run-time not compile-time) type of "a" determines what "Equals" method gets called. The called method merely has to work correctly when "b" is null. Unless the called method is extremely poorly written, the first step it does is determine whether "b" is a type that it understands.

So Object.Equals(a, b) is a reasonable compromise between readability/coding_effort and safety.

Timberwork answered 26/9, 2013 at 23:30 Comment(0)
S
2

The framework guidelines suggest that you treat Equals as value equality (checking to see whether two objects represent the same information, i.e. comparing properties), and == as reference equality, with the exception of immutable objects, for which you should probably override == to be value equality.

So, assuming the guidelines apply here, pick whichever is semantically sensible. If you're dealing with immutable objects, and you expect both methods to produce identical results, I'd use == for clarity.

Survive answered 17/8, 2010 at 22:16 Comment(1)
The Microsoft guidelines do NOT address the issue of nulls, which is what the question is about. Most importantly, while "myString == null" is a safe test, "myString.Equals(null)" will cause an exception when myString is null. Also, the Microsoft guidelines don't even mention the difference between Object.Equals(myObject, b) and myObject.Equals(b). The former is robust; the latter gives an exception if myObject is null. So, while the link you provide is useful, it is NOT an answer to the poster's question.Timberwork
A
1

In reference to "...writing code that will handle objects outside the author's control...", I would point out that both static Object.Equals and the == operator are static methods and therefore cannot be virtual/overridden. Which implementation gets called is determined at compile time based on the static type(s). In other words, there is no way for an external library to provide a different version of the routine to your compiled code.

Alic answered 17/8, 2010 at 22:26 Comment(7)
This statement is not entirely correct. It is possible for the implementation of operator == on a type to call a virtual method on one of the instances involved. Which means it actually is possible for things to happen that are not based on the types in the expression, but rather on the runtime types involved.Allcot
And the same applies to the static object.Equals(a,b) method too, which does something like ((object)a == (object)b) || (a != null && b != null && a.Equals(b)) that is, it could call the virtual Equals method on object a.Macronucleus
@Allcot Your point is valid, however that does not equal (pardon the pun) my statement being 'not entirely correct'.Alic
I'm with you up until the statement: "there is no way for an external library to provide a different version of the routine to your compiled code". As I mention in my comment, and @Macronucleus in his, it is possible for virtual methods to run as a result of either Object.Equals(a,b) or a == b.Allcot
@LBushkin: We're arguing semantics here, but as long as we're arguing :-) : I said the routine, i.e. either static Object.Equals or the '==' operator. What path of execution the routine takes can be affected by many factors, including, yes, the use of virtual methods by the aforementioned routine. Of course, this unlikely scenario could only happen if the routine determined at compile time actually used virtual methods.Alic
@Daniel +1 THANK YOU. This is a very subtle screw-up in Microsoft's design. I just verified that "==" fails to call my subclass's version, when compile-time type is more general. NOTE HOWEVER that "Object.Equals" is a static method in the OBJECT class, and that DOES successfully call mySubclass.Equals(Object other) method, if one exists. Readers should be aware that it may not directly call IEquatable version mySubclass.Equals(MySubclass other), if it is present. So it is essential to write the general one (Object other) to call the specific (MySubclass other), when appropriate.Timberwork
@LBushkin: If a type overrides Equals(Object), the static Object.Equals(Object,Object) method will call that override on one of the objects unless the other is null; this will work no matter how its argument is typecast. By contrast, even if a type overloads ==, it cannot guarantee that the overload will be used if it's cast to Object.Exit
W
0

Do this as two nulls can not be compared

(a == null ? "NA" : a).equals(b == null ? "NA" : b)
Wrath answered 22/9, 2023 at 10:55 Comment(0)
D
-1

I ended up here when I was trying to compare the unique Id of objects that could themselves be null. Found it easier to just impute the missing data first, and then do the comparison.

Guid currentId = (Object1 == null) ? Guid.Empty : Object1.Id;
Guid newId = (Object2 == null) ? Guid.Empty : Object2.Id;
If (currentId == newId)
{
    //do happyface
}
else
{
   //do sadface
}
Doner answered 29/10, 2020 at 3:48 Comment(2)
This is not an answer to the OP's question.Standpoint
This "answer" is not relevant to the current discussion.Timberwork

© 2022 - 2024 — McMap. All rights reserved.