Struct equality performance in .NET
A struct type is a value type that is typically used to encapsulate small groups of related variables. Struct
s 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:
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.
- MA0065 - Default ValueType.Equals or GetHashCode is used for struct's equality
- MA0066 - Hash table unfriendly type is used in a hash table
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!