Equality Comparisons Two types of Equality:
- Equivalence (value equality)
- Identity (reference equality)
Reference Equality (Identity)
Objects have the same identity (or reference equality) if they refer to the same location in memory.
Comparisons
Use ReferenceEquals
:
areEqual = Object.ReferenceEquals(var1, var2);
Notes
Never test for reference equality on strings. Although string is a reference type, its equality operators have been overridden to make them behave like value types.
Value Equality (Equivalence)
Objects have the same equivalence (or value equality) based on abstract definitions. In some cases, the definition is that:
- They must be of the same type
- All of their fields and properties must have the same value
Five Guarantees of Equivalence
Assuming that x
, y
, and z
are not null:
x.Equals(x) == true
(reflexive property)x.Equals(y) == y.Equals(x)
(symmetric property)if (x.Equals(y) && y.Equals(z) == true
, thenx.Equals(z) == true
(transitive property)x.Equals(y)
is omnipotent- Any non-null value != null
Notes
If you use ReferenceEquals
to test two variables for value equality, the result will always be false (because each variable is boxed into a separate object instance).
Implementing Value Equality in Classes and Structs
- Implement
System.IEquatable<T>
interface by providing a type-specificEquals
method- Do not throw exceptions from this method.
- This method must examine only fields that are declared in the class.
- This method must call base.Equals to examine fields in the base class.
- If the type inherits directly from Object, do not call base.Equals (Object’s implementation of Equals performs a reference equality check).
- Override the virtual
Object.Equals(Object)
method - (optional) Overload the == and != operators
- Override Object.GetHashCode such that two objects of this type that have value equality produce the same hash
- (optional) Implement IComparable
interface and overload <= and >= operators
Example Implementation
class Person : IEquatable<Person>
{
public int Age { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Person(string firstName, string lastName, int age)
{
this.FirstName = firstName;
this.LastName = lastName;
this.Age = age;
}
// #2
public override bool Equals(object obj) => this.Equals(obj as Person);
// #1
public bool Equals(Person p)
{
if (p is null)
return false;
// Optimization of a common success case:
if (Object.ReferenceEquals(this, p))
return true;
if (this.GetType() != p.GetType())
return false;
// #1(b)
return (Age == p.Age) && (FirstName == p.FirstName) && (LastName == p.LastName);
}
// #4
public override int GetHashCode() => (Age, FirstName, LastName).GetHashCode();
// #3
public static bool operator ==(Person lhs, Person rhs)
{
if (lhs is null)
{
if (rhs is null)
return true;
// Only left side is null:
return false;
}
}
return lhs.Equals(rhs);
public static bool operator !=(Person lhs, Person rhs) => !(lhs == rhs);
}