Understanding Equality in C# with IEquatable, Not Equals and ==

Understanding Equality in C# with IEquatable, Not Equals and ==

posted 2 min read

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.

  1. 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/

If you read this far, tweet to the author to show them you care. Tweet a Thanks
Great guide on IEquatable vs Equals and records! Really helps to understand when to choose each approach. Quick question—how would you handle custom logic in the Equals method if you need to compare objects with more complex properties like collections? And are there any potential downsides to using records in large projects? Cheers its helpful.........!!
Thanks, Ben! Glad you found it helpful!
For collections in Equals, you can use SequenceEqual()
https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.sequenceequal?view=net-9.0
for lists or ToHashSet().SetEquals() for unordered sets.
https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.hashset-1.setequals?view=net-9.0
If the collection has custom objects, make sure they also override Equals.
Regarding records, a downside in big projects is that they create new copies on modification, which can lead to extra memory use.
Great, thanks! I really appreciate you asking because it helps us all improve our knowledge and brings great value to the community. Discussions like these make learning even better!
Hope that helps! Let me know if you have more questions

More Posts

Building Robust API Clients in C# with Refit

Odumosu Matthew - Jan 6

Modern Use OF Telnet with an example In dotnet,The Rise and Fall of Telnet: A Network Protocol's Journey

Moses Korir - Mar 12

Unlock the Power of the Static Keyword in C#!

Hussein Mahdi - Sep 22, 2024

Exploring Modern Web Development with Blazor: A Friendly Guide

Kenneth Okalang 1 - Mar 13

How To Create Custom Middlewares in ASP.NET Core

Anton Martyniuk - May 13, 2024
chevron_left