Implementing Equals and GetHashCode in C#
Equals and GetHashCode
If you want to compare your objects for equality, you’re going to have to implement Equals
and GetHashCode
.
There are plenty examples around of how to do this, but they tend to contradict each other.
This is the pattern that I follow.
Classes
There’s no point in implementing IEquatable<T>
for classes: just make sure that you override Equals(object)
and GetHashCode()
.
For GetHashCode()
I’ve used the primes recommended by Jon Skeet, although others may also work.
The amount of code you’ll have to write for the Equals
method depends on whether or not the class is sealed: if it isn’t, you’ll have to check .GetType()
to see whether you’re comparing against a subclass of your class (which might have different comparison rules).
Non-sealed
public class Foo
{
public int StructField { get; set; }
public string ObjectField { get; set; }
// ...
public override bool Equals(object obj)
{
// We use ReferenceEquals to avoid cases where == has been overridden.
if (ReferenceEquals(obj, null))
return false;
if (ReferenceEquals(this, obj))
return true;
if (this.GetType() != obj.GetType())
return false;
var other = (Foo)obj;
// For members which are value types, == if it's implemented, otherwise
// `this.Foo.Equals(other.Foo)`.
// For reference types, use `Equals(a, b)`, as it correctly handles null references.
return this.StructField == other.StructField &&
Equals(this.ObjectField, other.ObjectField);
}
public override int GetHashCode()
{
// Use unchecked, just in case the assembly is compiled /checked
unchecked
{
int hash = 17;
// Follow this pattern for all members which are used in Equals, but do not
// include any members which are not used in Equals.
hash = hash * 23 + this.StructField.GetHashCode();
hash = hash * 23 + this.ObjectField?.GetHashCode() ?? 0;
return hash;
}
}
// You wouldn't usually override == for classes (except perhaps if the class
// is immutable), as it's normally reserved for reference equality.
}
Sealed
public sealed class Foo
{
public int StructField { get; set; }
public string ObjectField { get; set; }
// ...
public override bool Equals(object obj)
{
var other = obj as Foo;
// We use ReferenceEquals to avoid cases where == has been overridden.
if (ReferenceEquals(other, null))
return false;
if (ReferenceEquals(this, other))
return true;
// For members which are value types, == if it's implemented, otherwise
// `this.Foo.Equals(other.Foo)`.
// For reference types, use `Equals(a, b)`, as it correctly handles null references.
return this.StructField == other.StructField &&
Equals(this.ObjectField, other.ObjectField);
}
public override int GetHashCode()
{
// Use unchecked, just in case the assembly is compiled /checked
unchecked
{
int hash = 17;
// Follow this pattern for all members which are used in Equals, but do not
// include any members which are not used in Equals.
hash = hash * 23 + this.StructField.GetHashCode();
hash = hash * 23 + this.ObjectField?.GetHashCode() ?? 0;
return hash;
}
}
// You wouldn't usually override == for classes (except perhaps if the class
// is immutable), as it's normally reserved for reference equality.
}
Structs
For structs, implementing IEquatable<T>
will avoid the need to box the struct when comparing equality.
You still need to implement Equals(object)
of course.
You don’t have to worry about sealed/non-sealed, since structs are implicitly sealed.
public struct Foo : IComparable<Foo>
{
// Structs should be immutable
public int StructField { get; }
public string ObjectField { get; }
// ...
public override bool Equals(object obj)
{
return (obj is Foo) && this.Equals((Foo)obj);
}
public bool Equals(Foo other)
{
return this.StructField == other.StructField &&
Equals(this.ObjectField, other.ObjectField);
}
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = hash * 23 + this.StructField.GetHashCode();
hash = hash * 23 + this.ObjectField?.GetHashCode() ?? 0;
return hash;
}
}
// You may also want to override == (and !=)
public static bool operator ==(Foo x, Foo y) => x.Equals(y);
public static bool operator !=(Foo x, Foo y) => !(x == y);
}
Helper Methods
There are a number of methods which I used to help implement Equals
and GetHashCode
.
SequenceEqual
As discussed above, if you want to test two objects for equality while being null-safe, you use Object.Equals(x, y)
.
However, there’s no equivalent for IEnumerable
types, and calling Enumerable.SequenceEqual(a, b)
in a null-safe way.
/// <summary>
/// Null-safe comparison between two IEnumerables, testing for the same elements in the same order
/// </summary>
/// <remarks>
/// This method is to <see cref="Enumerable.SequenceEqual{TSource}(IEnumerable{TSource}, IEnumerable{TSource})"/>
/// as <see cref="object.Equals(object, object)"/> is to <see cref="object.Equals(object)"/>.
/// </remarks>
public static bool SequenceEqual<T>(IEnumerable<T> item1, IEnumerable<T> item2, IEqualityComparer<T> comparer = null)
{
if (ReferenceEquals(item1, null))
return ReferenceEquals(item2, null);
if (ReferenceEquals(item1, item2))
return true;
return item1.SequenceEqual(item2, comparer);
}
GetHashCodeOfElements
Enumerable.SequenceEqual
is good for testing collection equality, but there’s no equivalent for getting the HashCode for a sequence of elements.
Here’s a helper which takes the order of elements into account.
/// <summary>
/// Gets a HashCode of a collection by combining the hash codes of its elements. Takes order into account
/// </summary>
/// <typeparam name="T">Type of element</typeparam>
/// <param name="source">Collection to generate the hash code for</param>
/// <returns>Generated hash code</returns>
public static int GetHashCodeOfElements<T>(this IEnumerable<T> source, IEqualityComparer<T> comparer = null)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (comparer == null)
comparer = EqualityComparer<T>.Default;
unchecked
{
int hash = 17;
foreach (var element in source)
{
hash = hash * 23 + comparer.GetHashCode(element);
}
return hash;
}
}
EqualsAnyOrder and GetHashCodeOfElementsAnyOrder
Enumerable.SequenceEqual
and GetHashCodeOfElements
are good for when the order of items in the collection matters, but sometimes you want to ignore order when testing for equality or generating HashCodes.
You can use a HashSet
for testing equality, but that can be improved on slightly.
Note that EqualsAnyOrder
will not accept null collections.
/// <summary>
/// Tests whether two collections have the same elements, in any order.
/// </summary>
/// <remarks>
/// Slightly cheaper than 'new HashSet{T}(source).SetEquals(other)' in some common, simple cases, but delegates to it
/// in the worst case.
/// </remarks>
/// <typeparam name="T">Type of element</typeparam>
/// <param name="source">Source collection</param>
/// <param name="other">Other collection</param>
/// <param name="comparer">Optional comparer to use to compare elements, defaults to the default EqualityComparer{T}</param>
/// <returns>True if both elements contain only the same elements, false otherwise</returns>
public static bool EqualsAnyOrder<T>(this IEnumerable<T> source, IEnumerable<T> other, IEqualityComparer<T> comparer = null)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (other == null)
throw new ArgumentNullException(nameof(source));
if (ReferenceEquals(source, other))
return true;
if (comparer == null)
comparer = EqualityComparer<T>.Default;
var sourceAsCollection = source as ICollection<T>;
var otherAsCollection = other as ICollection<T>;
if (sourceAsCollection != null && otherAsCollection != null)
{
if (sourceAsCollection.Count == 0 && otherAsCollection.Count == 0)
return true;
if (sourceAsCollection.Count != otherAsCollection.Count)
return false;
if (sourceAsCollection.Count == 1 && otherAsCollection.Count == 1)
return comparer.Equals(sourceAsCollection.First(), otherAsCollection.First());
}
return new HashSet<T>(source, comparer).SetEquals(other);
}
/// <summary>
/// Gets a HashCode of a collection by combining the hash codes of its elements. Returns the same hashcode for any order of elements
/// </summary>
/// <typeparam name="T">Type of element</typeparam>
/// <param name="source">Collection to generate the hash code for</param>
/// <returns>Generated hash code</returns>
public static int GetHashCodeOfElementsAnyOrder<T>(this IEnumerable<T> source, IEqualityComparer<T> comparer = null)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (comparer == null)
comparer = EqualityComparer<T>.Default;
var hashCodes = (source is ICollection<T> c) ? new List<int>(c.Count) : new List<int>();
foreach (var item in source)
{
hashCodes.Add(comparer.GetHashCode(item));
}
hashCodes.Sort();
unchecked
{
int hash = 17;
foreach (var hashCode in hashCodes)
{
hash = hash * 23 + hashCode;
}
return hash;
}
}