Implementing equality for reference objects using IEquatable and the == operator: summary
July 8, 2015 Leave a comment
We have looked at implementing IEquatable and overriding various equality-related methods and operators in various other posts on this blog. You can look at this page and scroll down to the section called “Comparison and equality” to view all of them.
In this post we’ll put all of these together and implement a joined equality strategy for our reference type.
We’ll start with a simple Animal class:
public class Animal { public string Name { get; set; } public int NumberOfLegs { get; set; } }
Let’s follow what we saw before about IEquatable and implement it along with object.GetHashCode and object.Equals. We say that two animals are equal if their names and number of legs are the same:
public class Animal : IEquatable<Animal> { public string Name { get; set; } public int NumberOfLegs { get; set; } public bool Equals(Animal other) { if (other == null) return false; return (other.Name == Name && other.NumberOfLegs == NumberOfLegs); } public override bool Equals(object obj) { if (obj == null) return false; if (obj is Animal) { Animal other = (Animal)obj; return this.Equals(other); } return false; } public override int GetHashCode() { return NumberOfLegs * Name.GetHashCode(); } }
Let’s perform some tests:
Animal animalOne = new Animal() { Name = "Fiffy", NumberOfLegs = 4 }; Animal animalTwo = new Animal() { Name = "Fiffy", NumberOfLegs = 4 }; Animal animalThree = new Animal() { Name = "Fiffy", NumberOfLegs = 6 }; Debug.WriteLine(animalOne.Equals(animalTwo)); Debug.WriteLine(animalOne.Equals(animalThree)); Debug.WriteLine(animalTwo.Equals(animalThree));
…gives
True
False
False
…which looks fine. Let’s now declare animalOne as an Object:
object animalOne = new Animal() { Name = "Fiffy", NumberOfLegs = 4 };
This will yield the same output. We’ll now override the == and != operators:
public class Animal : IEquatable<Animal> { public string Name { get; set; } public int NumberOfLegs { get; set; } public static bool operator ==(Animal animalOne, Animal animalTwo) { return object.Equals(animalOne, animalTwo); } public static bool operator !=(Animal animalOne, Animal animalTwo) { return !object.Equals(animalOne, animalTwo); } public bool Equals(Animal other) { if (other == null) return false; return (other.Name == Name && other.NumberOfLegs == NumberOfLegs); } public override bool Equals(object obj) { if (obj == null) return false; if (obj is Animal) { Animal other = (Animal)obj; return this.Equals(other); } return false; } public override int GetHashCode() { return NumberOfLegs * Name.GetHashCode(); } }
Do you remember how we overrode == and != from this post? In the case of the Animal class we simply call object.Equals. Why? Because the compiler will then direct the == and != calls to our overridden object.Equals method which already satisfies equality.
Let’s test this code with the same three Animal objects:
Debug.WriteLine(animalOne == animalTwo); Debug.WriteLine(animalOne == animalThree); Debug.WriteLine(animalTwo == animalThree);
This will print true, false, false like before. Let’s now consider a class called Giraffe which has an extra property: NeckLength. We consider two giraffes to be equal if all Animal properties are equal and their neck lengths are the same. Here comes a possible implementation of all equality-related methods for the Giraffe class:
public class Giraffe : Animal, IEquatable<Giraffe> { public int NeckLength { get; set; } public bool Equals(Giraffe other) { if (!base.Equals(other)) return false; return NeckLength == other.NeckLength; } public override bool Equals(object obj) { if (!base.Equals(obj)) return false; Giraffe giraffe = (Giraffe)obj; return this.Equals(giraffe); } public override int GetHashCode() { return base.GetHashCode() * NeckLength.GetHashCode(); } }
Let’s perform some tests:
Giraffe giraffeOne = new Giraffe() { Name = "Jack", NeckLength = 2, NumberOfLegs = 4 }; Giraffe giraffeTwo = new Giraffe() { Name = "Jack", NeckLength = 2, NumberOfLegs = 4 }; Giraffe giraffeThree = new Giraffe() { Name = "Jack", NeckLength = 3, NumberOfLegs = 4 }; Debug.WriteLine(giraffeOne.Equals(giraffeTwo)); Debug.WriteLine(giraffeOne.Equals(giraffeThree)); Debug.WriteLine(giraffeTwo.Equals(giraffeThree));
Prints true, false, false so it looks correct. Notice how we didn’t provide any extra overloads for the == and != operators. Can you guess if the below code returns the correct results?
Debug.WriteLine(giraffeOne == giraffeTwo); Debug.WriteLine(giraffeOne == giraffeThree); Debug.WriteLine(giraffeTwo == giraffeThree);
It will also return true, false, false. Why? The == and != overrides in the Animal superclass make sure that object.Equals is called. As we have implemented object.Equals in Giraffe as well we are safe without providing any extra logic for == and != in the derived class.
There’s in fact a simplified version for all this. If you have overridden the object.Equals and object.GetHashCode methods then there’s no need for the strongly typed IEquatable interface:
public class Animal { public string Name { get; set; } public int NumberOfLegs { get; set; } public static bool operator ==(Animal animalOne, Animal animalTwo) { return object.Equals(animalOne, animalTwo); } public static bool operator !=(Animal animalOne, Animal animalTwo) { return !object.Equals(animalOne, animalTwo); } public override bool Equals(object obj) { if (obj == null) return false; if (obj is Animal) { Animal other = (Animal)obj; return (other.Name == Name && other.NumberOfLegs == NumberOfLegs); } return false; } public override int GetHashCode() { return NumberOfLegs * Name.GetHashCode(); } } public class Giraffe : Animal { public int NeckLength { get; set; } public override bool Equals(object obj) { if (!base.Equals(obj)) return false; Giraffe giraffe = (Giraffe)obj; return this.NeckLength == giraffe.NeckLength; } public override int GetHashCode() { return base.GetHashCode() * NeckLength.GetHashCode(); } }
View all various C# language feature related posts here.