Background

This article is in continuation of the series of articles regarding how Equality works in .NET. The purpose is to give the developers a more clear understanding on how .NET handles Equality for different types. We have already seen equality for primitive types, how reference types work, and we also discussed separately how equality works differently for String type. Following are some points which we were able to understand so far.

Key Points Learned So Far

If the above points do not make sense to you, it would be better to read it from the start. Following are the links to the previous content related to it.

Equality Operator for Value Types

We have already learned what the Equality operator does for both the primitive types and reference types. One case that we haven’t tested yet is that what happens for the non-primitive value types. This time we will be focusing on the value types.

We will be using the same example that we used before, so we will declare a Person type as struct and we will compare two instances to see if they are equal not using Object.Equals method which we did previously and we know that it does the value comparison which is very in-efficient as for value types it uses reflection to iterate through the fields and check for equality of each one, but instead we will compare two Person objects using the == operator.

The Person type definition looks like,

  1. public struct Person {
  2. private string _name;
  3. public string Name {
  4. get {
  5. return _name;
  6. }
  7. }
  8. public Person(string name) {
  9. _name = name;
  10. }
  11. public override string ToString() {
  12. return _name;
  13. }
  14. }

If we write the following code in Main and build the project, what will we see,

  1. class Program {
  2. static void Main(String[] args) {
  3. Person p1 = new Person("Ehsan Sajjad");
  4. Person p2 = new Person("Ehsan Sajjad");
  5. Console.WriteLine(p1.Equals(p2));
  6. Console.WriteLine(p1 == p2);
  7. Console.ReadKey();
  8. }
  9. }

When we try to build the above program, the first line where we are comparing p1 and p2 using Equals method will have no problem, but the build fails on line 2 where we are using == operator with the following error message:

Operator ‘==’ cannot be applied to operands of type `Person` and `Person`

So the above makes one thing clear to us that the Equality operator does nothing for the non-primitive value types, for using the equality operator for non-primitive value types we need to provide an operator overload for the type.

Let’s modify the above example to add the equality operator overload for the Person struct, we will be now specifying what the == operator should do for two Person objects being compared, following is the syntax to provide the overload for == operator if we want to provide the implementation so that what the operator should do when used for two objects of type Person,

  1. public static bool operator == (Person p1, Person p2) {}

After adding the above code in the Person struct we will be able to compile the code written in the Main method of the Program, but note that this will not compile still as the overload has return type bool as per signatures but we are returning nothing, this is just to give an idea how to write == operator overloaded implementation for a user defined Value Type.

We saw in one of the previous posts that String overloads the equality operator to make sure that it does the same thing as Equals method, so whenever you are defining a new type make sure that it does the same thing with both the method and the operator, if we provide either of them it is generally a good thing to do. Following is an example code that will help us understand why it is a good practice,

  1. class Program {
  2. static void Main(string[] args) {
  3. Tuple < int, int > tuple1 = Tuple.Create(1, 2);
  4. Tuple < int, int > tuple2 = Tuple.Create(1, 2);
  5. Console.WriteLine(ReferenceEquals(tuple1, tuple2));
  6. Console.WriteLine(tuple1.Equals(tuple2));
  7. Console.WriteLine(tuple1 == tuple2);
  8. Console.Read();
  9. }

As you can see we are instantiating two tuples containing same values i.e. 1 and 3, Tuple is a generic class which comes prebuilt in Framework Class Libraries provided by Microsoft which simply provides a way to group couple of values together in a single object. The Tuple.Create(1, 2); is a nicer way to instantiate a new tuple saving developer to explicitly write the generic types in the code

Now we are comparing the tuples to see if they are equal or not, Tuple is a reference type, so we are checking equality using ReferenceEquals check to confirm that we are dealing with two separate instances, next we are comparing if they are equal using the Equals method, and lastly we are comparing using equality operator aka == operator. Let’s run this program and following is the output of the above program,


For some of you the result might be a surprise, we can see that ReferenceEquals has returned false which means that both are different instances, but the == operator and Equals method have returned the opposite result, the == operator says that both are not equal while the Equals method is saying that they are equal.

What’s actually happening above is that Tuple overrides the Equals method in a way that it checks for value equality for the objects. Microsoft figured that when you are dealing with a type whose purpose is just to encapsulate a couple of fields that is probably what you want equality to mean, so as the two Tuples have the same value the Equals method says that they are equal, but Microsoft didn’t provide the overload for == operator and that means that == operator has just done what it is meant to be doing and will always do for reference type that does not provide an overload and checks reference equality and has returned False in this case, as both are different instances.

I am pretty sure that has confused you, of course the behavior is confusing and it did confuse me as well when I was digging in to it. Almost no one is going to expect this kind of behavior and it is strongly recommended to not add this kind of behavior in any type we define.

If you override the Equality then it is much better to provide the == operator overload to make sure that method and the operator always gives the same result and if you implement the IEquatable<T> interface then you should do same for that as well.

Comparing == Operator and Object.Equals Method

Let’s quickly see how the == operator and Equals method differ as far as their behavior is concerned:

So after all the above points and discussion, we can conclude that a lot of the time the operator and the method gives the same result in practice, but since the syntax of the operator is so much more convenient developers most of the time prefer the operator. In the next post we will be discussing what are the situations where == operator might not be the preferable but instead Equals method is.