Equality is a crucial concept in C#, and handling it efficiently can improve performance and correctness in applications. In this article, we'll focus on IEquatable, why it is preferable over Equals and ==, and how records in C# inherently support value-based equality.
1. Implementing IEquatable Instead of Equals
The IEquatable interface provides a strongly-typed method for checking equality and avoids unnecessary boxing, making it more efficient than the default Equals(object) method or the == operator.
Example Implementation:
class Product : IEquatable<Product>
{
public int Id { get; set; }
public string Name { get; set; }
public bool Equals(Product other)
{
if (other == null) return false;
return this.Id == other.Id && this.Name == other.Name;
}
public override bool Equals(object obj) => Equals(obj as Product);
public override int GetHashCode() => HashCode.Combine(Id, Name);
}
Why Use IEquatable Instead of Equals or ==?
Performance: Avoids boxing and type conversions that occur with Equals(object).
Type Safety: Prevents errors from implicit conversions when using ==.
Reliability: Works efficiently with generic collections like HashSet and Dictionary<TKey, TValue>.
Improved Hash Code Quality: HashCode.Combine improves the diffusion of hash codes across a large range, which is particularly useful for simple data types like integers, reducing hash collisions in data structures.
2. How Records Provide Built-in Value-Based Equality
n C# 9 and later, record types automatically implement value-based equality, meaning that two records with the same data are considered equal without requiring manual overrides.
Example with Records:
public record PersonRecord(string Name, int Age);
var person1 = new PersonRecord("Alice", 30);
var person2 = new PersonRecord("Alice", 30);
bool areEqual = person1 == person2; // true (compares values, not references)
Why Records Already Support Equality:
Value-based Comparison: Unlike classes, records automatically override Equals() and GetHashCode() to compare field values instead of object references.
Less Boilerplate Code: No need to implement IEquatable manually.
Immutable by Default: Encourages immutability, reducing side effects and unintended modifications.
Works Like Structs in Equality: Similar to structs, records perform value-based equality comparisons rather than reference-based, ensuring that two records with identical values are considered equal, regardless of their memory locations.
Thread Safety: Since records are immutable by default, they are inherently thread-safe, reducing the risks associated with concurrent modifications.
- Choosing Between IEquatable, Equals, and

4. Conclusion
For custom classes that require efficient equality comparison, implementing IEquatable is a great choice instead of relying on Equals or ==.
However, if you're working with immutable data structures, records provide built-in value-based equality, reducing complexity and improving maintainability.
Since records work similarly to structs in equality comparison, they are an excellent choice when value-based comparisons are essential.
Additionally, using HashCode.Combine improves hash distribution, making it more suitable for use in collections like HashSet and Dictionary<TKey, TValue>. Furthermore, since records are immutable by default, they provide inherent thread safety, making them ideal for concurrent programming scenarios.
Choosing the right approach depends on whether you need fine-grained control over equality or prefer a simpler, out-of-the-box solution with records.
References
Microsoft Docs: IEquatable Interface
https://learn.microsoft.com/en-us/dotnet/api/system.iequatable-1?view=net-9.0
Microsoft Docs: Record Types
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/record
Microsoft Docs: HashCode.Combine Method
https://learn.microsoft.com/en-us/dotnet/api/system.hashcode.combine?view=net-9.0
Microsoft Docs: Value Types and Reference Types
https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/types/