Struct equality performance in .NET

 
 
  • Gérald Barré

A struct type is a value type that is typically used to encapsulate small groups of related variables. Structs inherit from System.ValueType. This type overrides Equals and GetHashCode. The implementation of Equals calls Equals on each field and returns true if all fields are equal. If there are no GC references in this object it avoids reflection and uses memcmp (code). GetHashCode is a little bit complex. It looks for the first non-static field and gets its hashcode. If the type has no non-static fields, it returns the hashcode of the type.

The default implementation is very generic and works for any value type. The drawback is that the implementation is not performant! The Equals and GetHashCode methods are used when you check 2 instances are equal (a == b or a.Equals(b)) or when you use the type as the key of a HashSet<T>, a Dictionary<TKey, TValue>, or a similar type. This means that when you use a HashSet<T> where T is a struct with the default implementation, the performance may be sub-optimal. A quick benchmark shows how slow Equals and GetHashCode are compared to specific implementation when used with a HashSet<T>:

Source: https://gist.github.com/meziantou/605934eb7376d9c3cc46af0adad937e6

Now, you understand how important it is to override Equals and GetHashCode if you want those to be performant!

#Generate equality members

Instead of writing the Equals and GetHashCode methods yourself, you can generate them. Besides, you can generate operators and implement IEquatable<T>

This will produce the following code:

C#
internal struct StructWithOverrides : IEquatable<StructWithOverrides>
{
    public int Value;

    public StructWithOverrides(int value)
    {
        Value = value;
    }

    public override bool Equals(object obj)
    {
        return obj is StructWithOverrides overrides && Equals(overrides);
    }

    public bool Equals(StructWithOverrides other)
    {
        return Value == other.Value;
    }

    public override int GetHashCode()
    {
        return HashCode.Combine(Value);
    }

    public static bool operator ==(StructWithOverrides left, StructWithOverrides right)
    {
        return left.Equals(right);
    }

    public static bool operator !=(StructWithOverrides left, StructWithOverrides right)
    {
        return !(left == right);
    }
}

It is just a matter of seconds to get a performant struct!

#Detect missing overrides using a Roslyn Analyzer

You can check if you should implement Equals and GetHashCode for structs in your applications using a Roslyn analyzer. The good news is the free analyzer I've made already contains rules for that: https://github.com/meziantou/Meziantou.Analyzer.

You can install the Visual Studio extension or the NuGet package to analyze your code:

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee💖 Sponsor on GitHub