I recently wrote a blog post demonstrating how to define a class that allows equality to be based on the definition provided by the developer, not the .NET Framework.
To my surprise, I received comments, twitters, facebook posts, and emails all challenging my code sample. Some of the information I received caused me to (ahem) update my code sample (see below). However, some of the feedback made me wonder if the purpose behind overriding Equals is understood by some developers. Reading someone’s opinion in a book is one thing. Actually (and successfully) implementing working solutions doing it is another.
To begin with, let me make clear that the default implementation for equality of two reference types is to see if they are the same reference in memory itself. If overriding the Equals method off of Object was considered taboo, then it wouldn’t be a virtual method. As a developer, I may want control over default and ad-hoc behaviors of my objects when sorted, compared, or otherwise.
Take for example the Distinct extension method in the Enumerable class. This could easily be called in a Linq statement, and comparing references to one another may not solve my business problem. If my business scenario mandates that two (or more) students object instances are the same if each have the same ID, then so be it. Business rules drive code – code does not drive business rules. Whether or not someone agrees with the business rules is another matter.
Having said that, I do agree that the instance level data being used in the hash algorithm should be based on immutable data, so I revised the sample from the previous blog post to demonstrate that specific implementation.
public class Student : IEquatable<Student>
{
public readonly int Id;
public string FullName { get; set; }
public Student(int id)
{
this.Id = id;
}// ctor
public override string ToString()
{
return string.Format("{0}: {1}",
Id, FullName);
}// method
public override int GetHashCode()
{
return Id;
}// method
public override bool Equals(object obj)
{
Student other = obj as Student;
return ((IEquatable<Student>)this).Equals(other);
}// method
bool IEquatable<Student>.Equals(Student other)
{
if (object.Equals(other, null)) return false;
return (GetHashCode() == other.GetHashCode());
}// explicit interface implementation
public static bool operator ==(Student s1, Student s2)
{
if (Object.Equals(s1,null))
return (Object.Equals(s2,null));
return ((IEquatable<Student>)s1).Equals(s2);
}// operator overload
public static bool operator !=(Student s1, Student s2)
{
return !(s1 == s2);
}// operator overload
}// class
a2778f04-e54c-4e1e-a53e-faa99b2cf370|1|5.0