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)
{
    // See http://stackoverflow.com/a/670068/1086121

    if (source == null)
        throw new ArgumentNullException(nameof(source));

    if (comparer == null)
        comparer = EqualityComparer<T>.Default;

    int hash = 0;
    // Stores number of occurences so far of each value.
    var valueCounts = new Dictionary<T, int>();

    foreach (var element in source)
    {
        int curHash = comparer.GetHashCode(element);
        int bitOffset;
        if (valueCounts.TryGetValue(element, out bitOffset))
        {
            valueCounts[element] = bitOffset + 1;
        }
        else
        {
            valueCounts.Add(element, bitOffset);
        }

        // The current hash code is shifted (with wrapping) one bit
        // further left on each successive recurrence of a certain
        // value to widen the distribution.
        // 37 is an arbitrary low prime number that helps the
        // algorithm to smooth out the distribution.
        unchecked
        {
            hash += hash + ((curHash << bitOffset) | (curHash >> (32 - bitOffset))) * 37;
        }
    }

    return hash;
}