Type Comparison In C#
C# has a lot of operators and several of them are used to compare values. This is a very common task when programming - to check how two or more values relate to each other.
In this post, we will discuss different types of comparison methods, such as equality operators, object.equals method and IEquatable interface, used for comparing values in C#. You probably already know some of these concepts, but have a look anyway and see if you learn something new!
Contents
- C# Identity Equality
- C# Value Equality
- C# Override Equals
- C# IEquatable
- C# GetHashCode
- C# Equality Operators
Here is a bit of a quiz: What does this program print?
If you said true
then, unfortunately, you would be incorrect – this program prints false
. Before I explain why this program prints false
I would like to make one slight change to this program to make it print true
.
Did you spot it?
If you look carefully you will notice that I changed the Person type to be a struct instead of a class. Why would this make a difference? To understand let’s take a brief tour of how C# (and the CLR) handle equality.
Whenever you create a type without overriding the Equals method or implementing the IEquatable<T>
interface you relinquish control over how two instances of your type will be compared and accept the CLR’s default comparison mechanisms. If your type is a reference type (a class) you will get identity equality and if your type is a value type (a struct) you will get value equality.
C# Identity Equality
Reference types are called reference types because the object itself is stored on the managed heap and you access that object via a reference to that object.
When you compare two instances of a reference type (like the Person type in my first example) the CLR will compare the references to the objects to see if the references point to the same object. Two reference types will only be equal if their reference points to the same object on the managed heap. If the references are different then objects are not equal – even if their fields are identical.
Identity equality asks this question:
Do these two references point to the same object on the managed heap?
C# Value Equality
Value equality is a different process but is much simpler to understand. Value equality takes all instance fields of a value type and compares them to the instance fields of a second instance in respective order. I would imagine that value equality works much the way most developers expect all equality checks ought to.
Value equality asks this question:
For each field in these two instances, are all the values equal?
So does this mean that to have value equality we must use value types? Of course not! Let’s the proper way to implement value equality semantics with reference types by overriding the Object.Equals method.
C# Override Equals
Every time you use the binary equality operator (==) or the Equals method on a reference type you are invoking Object.Equals for the instances in question. If you wish to provide value equality the most obvious thing to do would be to override System.Object.Equals and use this method to compare the fields of your two instances. Let us begin by revisiting our Person type which I have refactored to make these examples a bit more interesting:
We have a class with two properties – let’s override the Equals method:
Now we have value equality for our reference type. While this is a simple solution, it is not ideal for the following reasons:
This approach is not type-safe. Since the Equals method accepts an argument of type Object we cannot guarantee that the instance that was passed to this method is a Person.
This approach is not “null safe”. Any comparisons with null will throw a NullReferenceException.
The null safety issue is easy enough to fix:
But how do we preserve type safety? Meet the IEquatable
C# IEquatable
This interface was designed specifically to help us tackle the type safety issue that we are facing. It declares a single member:
As you can see, this interface gives us the ability to create a strongly-typed override of our existing Equals method. Implement the interface like this:
IEquatable C# Example
Now that we have a strongly-typed Equals method any equality comparisons that are done on two instances of our type will be type-safe and null-safe. Using the as
cast in the default overridden implementation of Equals allows us to pass either an instance of Person or null and our implementation of IEquatable<T>.Equals
returns false which ensure that our methods won’t fail for null. For more information on the as
operator, see my blog post Is And As Operators In C#.
C# GetHashCode
GetHashCode method is an essential part of identity equality checks. A hash code is an integral value that represents the state of the current instance.
Basically, if two instances have the same hash code, they may be equal in terms of value. But if two objects do not have the same hash code they are most certainly not equal in terms of value.
This method allows our calling code a performance boost by not having to call Equals if the hash codes do not match.
As for the proper or best way to generate a hash code for an object instance, that is a discussion for another day. For now, I will add a simple GetHashCode implementation to our example to complete the exercise.
All we are doing here is taking two coprime numbers (23 and 37) and using them to manipulate the hash codes of our instance’s state to arrive at a final integral value.
Again, how the implementation works is not important at this point, what is important is that we are providing some implementation so that we can reap the performance benefits that GetHashCode can provide.
Now we have a class that properly provides value equality semantics. I hope I have shown not only how to implement this pattern in your code but also why it is important and necessary in the first place.
C# Equality Operators ( ==
and !=
)
The ==
(equality) and !=
(inequality) operators check if their operands are equal or not. A user-defined type can overload the ==
and !=
operators. If a type overloads one of the two operators, it must also overload another one.
Here is a complete example that includes those operator overloads:
Subscribe to Code with Shadman
Get the latest posts delivered right to your inbox