Comparing Values for Equality in .NET: Identity and Equivalence (Part 2)

Continued from Part 1:

ii) object.Equals(a, b)

object.Equals(a, b)): Overview

object.Equals(a, b) is a static method on the object class. Jeffery Richter describes it as ‘a little helper method’. It’s easiest to think of it as a method that does some checking for nulls and then calls a.Equals(b).

The reason it exists is that if a is null a call to a.Equals(b) will throw a NullReferenceException. If there’s a possibility that a will be null it is easier to call this method than explicitly check for the null. If a can’t be null there’s no need for the additional check and a call to a.Equals(b) will be better.

object.Equals(a, b)): Detail

In detail, this method does the following for a call to object.Equals(a, b):

a) Check if a and b are identical (i.e. they refer to the same location in memory or are both null). If so return true.
b) Check if either of a and b is null. We know they are not both null otherwise the routine would have returned in a) above, so if either is null return false.
c) Both a and b are not null: return the value of a.Equals(b).

object.Equals(a, b)): Value Types and Reference Types

Since a and b can’t be null for value types, object.Equals(a, b) is identical to a.Equals(b). In general you should call a.Equals(b) in preference to object.Equals(a, b) for value types.

For reference types as discussed above you should call this method if there’s a chance that a will be null in a call to a.Equals(b).

object.Equals(a, b): Override or not?

object.Equals(a, b) is a static method on System.Object, and consequently can’t be overridden. However, since it calls into a.Equals(b) any overrides of Equals will affect calls to this method as well.

iii) object.ReferenceEquals(a, b)

object.ReferenceEquals(a, b)): Overview

Whilst the two incarnations of Equals() above check for identity or equivalence depending on the underlying type, ReferenceEquals is intended to always check for identity.

object.ReferenceEquals(a, b)): Value Types and Reference Types

For reference types object.ReferenceEquals(a, b) returns true if and only if a and b have the same underlying memory address.

In general we shouldn’t care whether value types occupy the same underlying memory address. It isn’t relevant for anything we’d want to normally use them for. But the definition above gives us a problem when we come to value types being compared with Reference Equals

The difficulty comes from the fact that ReferenceEquals expects two System.Objects as parameters. This means that our value types will get boxed onto the heap as they are passed in to this routine. Normally, because of the way the boxing process works, they will get boxed separately to different memory addresses on the heap. This of course means the call to ReferenceEquals returns false.

So for example object.ReferenceEquals(10, 10) returns false, for these reasons.

You can see it’s the boxing that causes the problem in the following code:

// Set up value type in int variable – no boxing
int value = 10;
object one = value; // Cast to object so boxed
object two = value; // Cast again so boxed again separately
// one and two are now separate memory locations on the heap
Console.WriteLine(object.ReferenceEquals(one, two)); // false

// Set up value type in object variable which immediately boxes it onto the heap
object value2 = 10; // value is boxed already
object three = value2; // three points to the boxed value
object four = value2; // four also points to the same boxed value
Console.WriteLine(object.ReferenceEquals(three, four)); // true

object.ReferenceEquals(a, b): Override or not?

ReferenceEquals is a static method on object, and so once again cannot be overridden. It will always perform identity checks as outlined above.

iv) a == b

a == b: Overview

== is an operator, clearly, and not a method. In my humble opinion it has been included in C# largely as a syntactic convenience and to make the language look like C/C++.

As with a.Equals(b), == is intended to test for identity or equivalence as appropriate (see the discussion in the paragraph “What type of Equality do we expect?” in Part 1). In fact, in almost all circumstances == should behave like a.Equals(b).

a == b: Value Types

For value types within the .NET Framework, == is implemented as you would expect, and will test for equivalence (value equality). However, for any custom value types you implement (structs) a default == will not be available unless you provide one.

a == b: Reference Types

For reference types a default == is available, and this will test for identity (reference equality). For most reference types in the .NET Framework == will again test for identity, but, as for a.Equals(b), there are certain classes where the operator has been overloaded to do a value comparison. System.String is once again the canonical example, for the reasons discussed in part one of this article.

a == b: Override (overload?) or not?

Since == is an operator we can’t override it. However, we can overload it to provide different functionality to the base functionality described above.

For reference types Microsoft recommend that you don’t overload == unless you have reference types behaving as value types as discussed above. This means that even if you override a.Equals(b) to provide some custom functionality you should leave your == operator to provide an identity test. This is, I think, the only occasion where == should behave differently from a.Equals(b).

For value types, as mentioned above, a default overload of == will not be available and you will have to provide one if you need one. The easiest thing to do is simply to call a.Equals(b) from an operator overload in your struct: in general your implementation of == should not be different from a.Equals(b).

Note that if you overload == you should overload !=. You should also override a.Equals(b) to do the same thing, and as a result should overload GetHashCode. Finally you should consider overriding IComparable.CompareTo().

Part 3 to follow…

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s