
Hacktoberfest and the promise of free t-shirts had me looking for a project to help with this month. That’s how I stumbled across GeneGenie.Gedcom, a genealogical library written in C# (read more about it here), and found myself reviewing everything I know about class equality.
I focused on implementing some logic to make sure changes were correctly detected, and in order to do that I had to define “equality” for each class that represented some facet of genealogical research. Why was that? Let’s take two instances of a class – one with data that’s fresh from the database, and another that’s potentially been modified by the user. Any inequality between the two indicates one or more changes. That could be really important when, for example, it comes time to save changes.
So what does it mean to define equality in C#? And when do you have to define it yourself, versus using the default?
Contents
- 1 What do we get, out of the box?
- 2 Defining Your Own Equality
- 3 Interfaces and Equality as a Contract
- 4 Defining Your Own Contracts
What do we get, out of the box?
Everything derives from the base object
class in C#. In fact, you could define your own class like this.
public class Person : Object
{
// properties go here
}
Doing that wouldn’t make sense since it’s implied anyway, but it demonstrates that we get the object
class and everything that comes along with it by default. And one of those things is the Equals method. What’s it do for us?
[T]he Equals(Object) method tests for reference equality, and a call to the Equals(Object) method is equivalent to a call to the ReferenceEquals method. Reference equality means that the object variables that are compared refer to the same object. ~ MSDN
In other words, the default test for “equality” is that two instances of a class are literally the same instance. This is true when two variables point to the same instance in memory. That’s what you get out of the box, without doing anything else yourself.
In the code below, p2 references the same instance as (is equal to) p1. Even though p3 has the same values for name and age, it’s not equal. There are cases when you want this behavior, but not often.
using System;
public class Program
{
public static void Main()
{
Person p1 = new Person { Name = "Jay", Age = 25 };
Person p2 = p1;
Person p3 = new Person { Name = "Jay", Age = 25 };
Console.WriteLine(p1.Equals(p2)); // True
Console.WriteLine(p1 == p2); // True
Console.WriteLine(p1.Equals(p3)); // False
Console.WriteLine(p1 == p3); // False
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
The rules are different for a struct
but I’m not even going to cover that… 8 years using C# professionally and I don’t recall creating my own struct once. If you’re interested though, read the MSDN doc I linked to above.
Defining Your Own Equality
Most of the time, you want to be in total control of exactly what makes your class equal.
Overriding Object.Equals
Since the base Object.Equals
method is marked as virtual
you can override it in any class that derives from Object
, which is… well, everything!
In the code below, the Person
class now overrides the base Equals
method and defines what “equal” means to it. Note how that changes the result of p1.Equals(p3)
since it’s now comparing the name and age instead of the reference.
using System;
public class Program
{
public static void Main()
{
Person p1 = new Person { Name = "Jay", Age = 25 };
Person p2 = p1;
Person p3 = new Person { Name = "Jay", Age = 25 };
Console.WriteLine(p1.Equals(p2)); // True
Console.WriteLine(p1 == p2); // True
Console.WriteLine(p1.Equals(p3)); // True
Console.WriteLine(p1 == p3); // False
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public override bool Equals(object obj)
{
var other = obj as Person;
if (other == null)
return false;
if (Name != other.Name || Age != other.Age)
return false;
return true;
}
}
Overloading the == operator
Once you override Object.Equals to define your own equality, most people are going to expect that the ==
operator will perform the same way and not produce different results.
In the code below, I’ve overloaded the ==
operator so that it follows the same logic as Equals. (Actually, it just calls Equals, which is even better. Whenever possible, stay DRY!)
Notice how p1 == p3
is true now.
using System;
public class Program
{
public static void Main()
{
Person p1 = new Person { Name = "Jay", Age = 25 };
Person p2 = p1;
Person p3 = new Person { Name = "Jay", Age = 25 };
Console.WriteLine(p1.Equals(p2)); // True
Console.WriteLine(p1 == p2); // True
Console.WriteLine(p1.Equals(p3)); // True
Console.WriteLine(p1 == p3); // True
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public override bool Equals(object obj)
{
if (!(obj is Person))
return false;
var other = obj as Person;
if (Name != other.Name || Age != other.Age)
return false;
return true;
}
public static bool operator ==(Person x, Person y)
{
return x.Equals(y);
}
public static bool operator !=(Person x, Person y)
{
return !(x == y);
}
}
Interfaces and Equality as a Contract
In some cases, certain components of the .NET framework use interfaces to define equality. Instead of relying on the virtual Equals
method, they use an interface as a contract to let you know what methods they expect your class to implement.
To explain that a little better, let’s look at an example using LINQ’s DISTINCT
method. According to the docs, it expects your class to implement the IEquatable<T>
interface (where T
is your class type), which forces you to include a bool Equals(T other)
method in your class.
Similar to SQL, the DISTINCT
method method is used to remove duplicates from a collection. When it comes to primitive types the work is done for you, because those classes already implement the interface like this:
public struct Int32 :
IComparable, IFormattable, IConvertible , IComparable<Int32>, IEquatable<Int32>
{
internal int m_value;
...
...
public bool Equals(Int32 obj)
{
return m_value == obj;
}
}
var distinctNumbers = new[] {4,3,5,4,3,7,3}.Distinct();
Console.WriteLine(string.Join(",", distinctNumbers));
// Output:
// 4,3,5,7
To do the same thing in your own class, you can implement the same interface. After you implement Equals(T)
you need to define GetHashCode
as well – per the docs, the hash codes of two instances need to be equal too.
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
Person p1 = new Person { Name = "Jay", Age = 25 };
Person p2 = p1;
Person p3 = new Person { Name = "Jay", Age = 25 };
var people = new List<Person> { p1, p3 };
Console.WriteLine(p1.Equals(p3));
Console.WriteLine(people.Distinct().Count());
}
}
public class Person : IEquatable<Person>
{
public string Name { get; set; }
public int Age { get; set; }
public bool Equals(Person other)
{
if (ReferenceEquals(other, null))
return false;
if (ReferenceEquals(this, other))
return true;
return Name.Equals(other.Name) && Age.Equals(other.Age);
}
public override int GetHashCode()
{
int hashName = Name == null ? 0 : Name.GetHashCode();
int hashAge = Age.GetHashCode();
return hashName ^ hashAge;
}
}
It’ll actually work if you just override Equals(object)
and leave out IEquatable<T>
, but I like that the interface defines a method that uses generics so you don’t have to unbox the object and check the type, etc. It saves a few steps.
Defining Your Own Contracts
You can take this all a step further by defining your own interfaces that extend IEquatable<T>
, or by creating abstract classes that force your class to implement certain functions.
Interfaces that extend interfaces
Let’s look at an example of extending an interface first. I’m tired of the “person” example, so let’s switch to the Legend of Zelda. :)
Here’s an example of two items that implement our own IWeapon<T>
interface, which in turn extends the IEquatable<T>
interface. Implement the one, and you get the other for free.
public class Sword : IWeapon<Sword>
{
public enum SwordLevel { Fighter, Master, Tempered, Golden }
public SwordLevel Strength { get; set; }
public bool Equals(Sword other)
{
return Strength == other?.Strength;
}
public bool IsEquipped { get; set; }
}
public class Shield : IWeapon<Shield>
{
public enum ShieldLevel { Fighter, Red, Mirror }
public ShieldLevel Strength { get; set; }
public bool Equals(Shield other)
{
return Strength == other?.Strength;
}
public bool IsEquipped { get; set; }
}
public interface IWeapon<T> : IEquatable<T>
{
bool IsEquipped { get; set; }
}
Abstract classes that implement interfaces
Here’s one more similar example, using an abstract class that implements the IEquatable<T>
interface. Now we can declare GetHashCode
as a member of the abstract class, forcing our other classes to implement it… less error-prone than using the interface directly.
public class Sword : Weapon<Sword>
{
public enum SwordLevel { Fighter, Master, Tempered, Golden }
public SwordLevel Strength { get; set; }
public override bool Equals(Sword other)
{
return Strength == other?.Strength;
}
public override int GetHashCode()
{
return Strength.GetHashCode();
}
public override void Use()
{
// attack!
}
}
public class Shield : Weapon<Shield>
{
public enum ShieldLevel { Fighter, Red, Mirror }
public ShieldLevel Strength { get; set; }
public override bool Equals(Shield other)
{
return Strength == other?.Strength;
}
public override int GetHashCode()
{
return Strength.GetHashCode();
}
public override void Use()
{
// block!
}
}
public abstract class Weapon<T> : IEquatable<T>
{
public abstract bool Equals(T other);
public abstract override int GetHashCode();
public bool IsEquipped { get; set; }
public abstract void Use();
}