C# Canonical Forms—Identity Equality
Reference Types and Identity Equality
What does it mean to say that a type is a reference type? Basically, it means that every variable of that type that you manipulate is actually a pointer to the actual object on the heap. When you make a copy of this reference, you get another reference that points to the same object. Consider the following code:
public class EntryPoint { static void Main() { object referenceA = new System.Object(); object referenceB = referenceA; } }
In Main(), I create a new instance of type System.Object, and then I immediately make a copy
of the reference. What I end up with is something that resembles the diagram in Figure 13-1.

Figure 13-1. Reference variables
In the CLR, the variables that represent the references are actually value types that embody
a storage location (for the pointer to the object they represent) and an associated type. However,
note that once a reference is copied, the actual object pointed to is not copied. Instead, you have
two references that refer to the same object. Operations on the object performed through one reference
will be visible to the client using the other reference.
Now, let’s consider what it means to compare these references. What does equality mean between two reference variables? The answer is, it depends on what your needs are and how you define equality. By default, equality of reference variables is meant to be an identity comparison. What that means is that two reference variables are equal if they refer to the same object, as in Figure 13-1. Again, this referential equality, or identity, is the default behavior of equality between two references to a heapbased object.
From the client code standpoint, you have to be careful about how you compare two object references for equality. Consider the following code:
public class EntryPoint { static bool TestForEquality ( object obj1, object obj2 ) { return obj1.Equals( obj2 ); } static void Main() { object obj1 = new System.Object(); object obj2 = null; System.Console.WriteLine( "obj1 == obj2 is {0}", TestForEquality(obj1, obj2) ); } }
Here I create an instance of System.Object, and I want to find out if the variables obj1 and obj2 are
equal. Since I’m comparing references, the equality test determines if they are pointing to the same
object instance. From looking at the code, you can see that the obvious result is that obj1 != obj2
because obj2 is null. This is expected. However, consider what would happen if you swapped the order
of the parameters in the call to TestForEquality(). You would quickly find that your program crashes
with an unhandled exception where TestForInequality() tries to call Equals() on a null reference.
Therefore, you should modify the code to account for this:
public class EntryPoint { static bool TestForEquality ( object obj1, object obj2 ) { if( obj1 == null && obj2 == null ) { return true; } if( obj1 == null ) { return false; } return obj1.Equals( obj2 ); } static void Main() { object obj1 = new System.Object(); object obj2 = null; System.Console.WriteLine( "obj1 == obj2 is {0}", TestForEquality(obj2, obj1) ); System.Console.WriteLine( "null == null is {0}", TestForEquality(null, null) ); } }
Now, the code can swap the order of the arguments in the call to TestForEquality(), and you
get the expected result. Notice that I also put a check in there to return the proper result if both
arguments are null. Now, TestForEqulity() is complete. It sure seems like a lot of work to test two
references for equality. Well, the designers of the .NET Framework Standard Library recognized this
problem and introduced the static version of Object.Equals() that does this exact comparison.
Thankfully, as long as you call the static version of Object.Equals(), you don’t have to worry about
creating the code in TestForEquality() in the previous example.
You’ve seen how equality tests on references to objects test identity by default. However, there may be times when this type of equivalence test makes no sense. Consider an immutable object that represents a complex number:
public class ComplexNumber { public ComplexNumber( int real, int imaginary ) { this.real = real; this.imaginary = imaginary; } private int real; private int imaginary; } public class EntryPoint { static void Main() { ComplexNumber referenceA = new ComplexNumber( 1, 2 ); ComplexNumber referenceB = new ComplexNumber( 1, 2 ); System.Console.WriteLine ( "Result of Equality is {0}", referenceA == referenceB ); } }
The output from the preceding code looks like this:
Result of Equality is False
Figure 13-2 shows the diagram representing the in-memory layout of the references.

Figure 13-2. References to ComplexNumber
This is the expected result based upon the default meaning of equality between references.
However, this is hardly intuitive to the user of these ComplexNumber objects. It would make better
sense for the comparison of the two references in the diagram to return true, since the values of the
two objects are the same. To achieve such a result, you need to provide a custom implementation of
equality for these objects. I’ll show how to do that shortly, but first, let’s quickly discuss what value
equality means.
|

