C# object equality is one of those topics that seems easy on the surface but can get a little complicated when you dig into it.
Reference vs. Value Equality
There are two ways to think about object equality:
- Reference Equality – The object references refer to the same instance in memory.
- Value Equality – The objects contain the same values. This is the generally-understood meaning of equality.
If two objects have reference equality, they also have value equality, but value equality does not guarantee reference equality.
To check for reference equality, use ReferenceEquals. To check for value equality, use Equals or ==.
Integer Example
The best way to demonstrate object equality is with an example. Imagine an “Integer” class which represents an integer:
public class Integer { public Integer() { } public Integer( int i ) { this.m_Value = i; } private int m_Value; public int Value { get { return this.m_Value; } set { this.m_Value = value; } } }
We will create a few Integer objects:
Integer int1a = new Integer( 1 ); Integer int1b = new Integer( 1 ); Integer int1c = int1a; Integer int2 = new Integer( 2 );
Reference Equality
To determine if two objects represent the same instance in memory, use the static method Object.ReferenceEquals. For example:
if (Object.ReferenceEquals( int1a, int1b )) { // do something }
In our example, “int1a” refers to the same instance as “int1a” and “int1c”, but not the same instance as “int1b” or “int2”, which were created as separate objects:
Object.ReferenceEquals( int1a, int1a ); // true Object.ReferenceEquals( int1a, int1b ); // false Object.ReferenceEquals( int1a, int1c ); // true Object.ReferenceEquals( int1a, int2 ); // false
Value Equality
To determine if two objects have the same value, use the == equality operator:
if (int1a == int2) { // do stuff }
Or you can use the Equals method:
if (int1a.Equals( int2 )) { // do stuff }
Override Equals Method
Note that the equality operator and Equals method are defined in the base Object class. But the base Object class has no understanding of the derived Integer class, so we need to override the Equals method as follows:
public override bool Equals( object obj ) { bool equals = false; if (obj != null) { if (obj is Integer) { equals = this.Value == ((Integer)obj).Value; } else if (obj is int) { equals = this.Value == (int)obj; } } return equals; }
Notice how we compare against both an “Integer” class, as well as a numerical “int” integer.
Overriding == and !=
By default, the == operator tests for reference equality. Therefore, to test for value equality, we must create == equality and != inequality operators. These are defined as static methods in the “Integer” class. In this case, we also define signatures that compare an “Integer” object to an “int” object, allowing an “int” to appear as both the left and right argument:
public static bool operator ==( Integer int1, Integer int2 ) { return int1.Equals( int2 ); } public static bool operator !=( Integer int1, Integer int2 ) { return !int1.Equals( int2 ); } public static bool operator ==( Integer int1, int int2 ) { return int1.Equals( int2 ); } public static bool operator !=( Integer int1, int int2 ) { return !int1.Equals( int2 ); } public static bool operator ==( int int1, Integer int2 ) { return int2.Equals( int1 ); } public static bool operator !=( int int1, Integer int2 ) { return !int2.Equals( int1 ); }
So you might expect these results:
int1a == 1; // true int1a == 2; // false 1 == int1a; // true 2 == int1a; // false int1a == int1b; // true int1a == int2; // false
The overloaded == operator should not throw exceptions. Any type that overloads the == operator should also overload the != operator.
Warning: A common error in == operator overloading is to use (a == b) or (a == null) within the overload. However, this simply calls the overloaded == operator, causing an infinite loop.
Equals Requirements
When overriding the Equals method, you must support these requirements:
- x.Equals(x) returns true
- x.Equals(y) returns the same value as y.Equals(x)
- if (x.Equals(y) && y.Equals(z)) returns true, then x.Equals(z) returns true
- Successive invocations of x.Equals(y) return the same value as long as the objects referenced by x and y are not modified
- x.Equals(null) returns false
- Equals() should never throw exceptions
GetHashCode
Also note that when you override Equals, you should typically override the GetHashCode method:
public override int GetHashCode() { return this.Value.GetHashCode(); }
Test Program
Here is a console test program to demonstrate this:
using System; namespace ObjectEquality { class Program { static void Main( string[] args ) { Integer integer1 = new Integer( 1 ); Integer integer1b = new Integer( 1 ); Integer integer1c = integer1; Integer integer2 = new Integer( 2 ); Console.WriteLine( "Object.ReferenceEquals( integer1, integer1 ): {0}", Object.ReferenceEquals( integer1, integer1 ) ); Console.WriteLine( "Object.ReferenceEquals( integer1, integer1b ): {0}", Object.ReferenceEquals( integer1, integer1b ) ); Console.WriteLine( "Object.ReferenceEquals( integer1, integer1c ): {0}", Object.ReferenceEquals( integer1, integer1c ) ); Console.WriteLine( "Object.ReferenceEquals( integer1, integer2 ): {0}", Object.ReferenceEquals( integer1, integer2 ) ); Console.WriteLine( "ninteger1 == 1: {0}", integer1 == 1 ); Console.WriteLine( "integer1 == 2: {0}", integer1 == 2 ); Console.WriteLine( "1 == integer1: {0}", 1 == integer1 ); Console.WriteLine( "2 == integer1: {0}", 2 == integer1 ); Console.WriteLine( "integer1 == integer1b: {0}", integer1 == integer1b ); Console.WriteLine( "integer1 == integer2: {0}", integer1 == integer2 ); Console.WriteLine( "ninteger1 != 1: {0}", integer1 != 1 ); Console.WriteLine( "integer1 != 2: {0}", integer1 != 2 ); Console.WriteLine( "1 != integer1: {0}", 1 != integer1 ); Console.WriteLine( "2 != integer1: {0}", 2 != integer1 ); Console.WriteLine( "integer1 != integer1b: {0}", integer1 != integer1b ); Console.WriteLine( "integer1 != integer2: {0}", integer1 != integer2 ); Console.ReadLine(); } public class Integer { public Integer( int i ) { this.Value = i; } public int Value; public override bool Equals( object obj ) { bool equals = false; if (obj != null) { if (obj is Integer) { equals = this.Value == ((Integer)obj).Value; } else if (obj is int) { equals = this.Value == (int)obj; } } return equals; } public static bool operator ==( Integer int1, Integer int2 ) { return int1.Equals( int2 ); } public static bool operator !=( Integer int1, Integer int2 ) { return !int1.Equals( int2 ); } public static bool operator ==( Integer int1, int int2 ) { return int1.Equals( int2 ); } public static bool operator !=( Integer int1, int int2 ) { return !int1.Equals( int2 ); } public static bool operator ==( int int1, Integer int2 ) { return int2.Equals( int1 ); } public static bool operator !=( int int1, Integer int2 ) { return !int2.Equals( int1 ); } public override int GetHashCode() { return this.Value.GetHashCode(); } } } }
Test Program Output
Here is the test program output:
Object.ReferenceEquals( integer1, integer1 ): True
Object.ReferenceEquals( integer1, integer1b ): False
Object.ReferenceEquals( integer1, integer1c ): True
Object.ReferenceEquals( integer1, integer2 ): Falseinteger1 == 1: True
integer1 == 2: False
1 == integer1: True
2 == integer1: False
integer1 == integer1b: True
integer1 == integer2: Falseinteger1 != 1: False
integer1 != 2: True
1 != integer1: False
2 != integer1: True
integer1 != integer1b: False
integer1 != integer2: True