In the previous article of this series about equality in C# I talked about different equality types and then discussed the reference equality. In this article I’m going one step forward and discuss the more complex value equality.
When to check for value equality?
When you define a class or struct, you decide whether it makes sense to create a custom definition of value equality (or equivalence) for the type. Remember from the previous article that the default Equals() method on the object class checks for reference equality. This means that in your classes you already have a default definition of equality. However, if you want to add custom types (for now I think about classes) to a collection and prevent value duplication you might need to implement your definition of equality. Or, simply put, you should ask yourself when are two “apples” equal?
Further, also please recall from the previous article that in case of structs (which are value types) the Equals() method is overridden to check for value equality instead of reference equality. So why would you want to override the definition of equality in your structs?
Well, the default implementation of value equality in structs uses reflection to examine all the fields and properties in the type. This means that the equality check will always produce correct results but it is very slow in comparison to a custom definition of value equality. In a very simplistic perspective the slower performance is due to the fact that the CLR doesn’t know beforehand what your properties are so it needs to use reflection to loop through all the struct properties. In a custom equality definition the developer declares that “Name” and “Age” properties for instance should match when two different struct objects are compared for equality. However, the full and detailed explanation is more complex and you can read it in this article.
Long story short: you should implement your custom definition of value equality both in classes (because the default implementation checks for reference equality) and in structs (because the default implementation of value equality doesn’t excel when it comes to performance)!
The five principles of equality
Implementing value equality seems to be very simple. And it is indeed! However, implementing value equality the right way requires that we adhere to three very important principles:
- Equality should be reflexive: x.Equals(x). Seems logical enough. An object should always be equal to itself.
- Equality should be symmetric: x.Equals(y) returns the same value as y.Equals(x)
- Equality should be transitive: if (x.Equals(y) && y.Equals(z)) returns true, then x.Equals(z) returns true
I know that the official C# documentation outlines five guarantees of equality but in my opinion the last two principles mentioned by the documentation will already be respected if these three are respected.
With this three principles implementing value equality in C# seems to bring more challenges than we would expect! Fair enough! However, there are a few basic things to consider when implementing a custom equality definition. If you follow the steps below, your custom definition of equality will adhere to the mentioned principles. So let’s say how to implement equality!
Implementing value equality in your types
To make sure you implement value equality in your types the right way take a note of the following steps:
- Implement the IEquatable<T> interface
- Override the GetHashCode() method
- Override the == and != operators
Let’s go a little bit deeper into all these steps.
1. Implement the IEquatable<T> interface
The IEquatable<T> interface defines a generalized method that a value type or class implements to create a type-specific method for determining equality of instances. The contract of this interface is fairly straightforward: you just need to implement the Equals() method defined in the interface. However, if you implement this interface you also need to override the Equals() method on the object class. Otherwise the behavior of your type won’t be consistent since your custom equality definition will check for value equality while the object Equals() method on the object class will check for reference equality. Here’s an example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | static void Main(string[] args) { var p1 = new Point(3, 5); var p2 = new Point(3, 5); //returns True Console.WriteLine($"Calling the interface implementation: {p1.Equals(p2)}"); //returns False //if you override object.Equals() both will return true Console.WriteLine($"Calling the Equals method on the base class: {object.Equals(p1, p2)}"); Console.ReadLine(); } |
Therefore, your implementation should look similar to the following code snippet:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | public override bool Equals(object obj) { return Equals(obj as Point); } public bool Equals(Point p) { // If parameter is null, return false. if (Object.ReferenceEquals(p, null)) { return false; } // Optimization for a common success case. if (Object.ReferenceEquals(this, p)) { return true; } // If run-time types are not exactly the same, return false. if (GetType() != p.GetType()) { return false; } // Return true if the fields match. return (X == p.X) && (Y == p.Y); } |
2. Override the GetHashCode() method
The GetHashCode() method in C# is used to compute a hash for each object. While hash collision is theoretically possible the object hash code is often used in reference to equality in C#. This means that there is a tight bond between your value equality definition and the hash code of each object.
When overriding the GetHashCode() method, the implementation should reflect your definition of equality. This means that the hash code should be computed using a pattern that includes all properties that you used in your equality definition. While there are countless discussions on what is the best pattern to use when generating the hash code for an object, here is a very basic example:
1 2 3 4 | public override int GetHashCode() { return X.GetHashCode() * 17 + Y.GetHashCode(); } |
3. Override the == and != operators
This step is optional but it’s a nice to have if you’ve gone this far with your type. Here’s a very basic example on how these overrides could look like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public static bool operator ==(Point lhs, Point rhs) { // Check for null on left side. if (Object.ReferenceEquals(lhs, null)) { if (Object.ReferenceEquals(rhs, null)) { // null == null = true. return true; } // Only the left side is null. return false; } // Equals handles case of null on right side. return lhs.Equals(rhs); } public static bool operator !=(Point lhs, Point rhs) { return !(lhs == rhs); } |
That’s mostly it. You should end up with a class definition that looks similar to this one:
This article is part of a series on equality in C#. Here are the other articles in the series.
- Part 1: Equality types and reference equality
- Part 2: Value equality (this article)
How useful was this post?
Click on a star to rate it!
Average rating / 5. Vote count:
Dan Patrascu-Baba
Latest posts by Dan Patrascu-Baba (see all)
- Configuration and environments in ASP.NET Core - 25/11/2019
- GraphQL in the .NET ecosystem - 19/11/2019
- A common use case of delegating handlers in ASP.NET API - 12/11/2019