Understand Virtual Object.Equals, Static Object.Equals and Reference.Equals in Object Class

Introduction

In this post, we will see how intelligently .NET handles equality and comparison out of the box. This means that you will be able to understand how .NET handles some of the issues that we discussed in the previous article.

If you remember from the previous post, there are four methods in the Object class which is provided by the .NET framework for the purpose of equality checking, but each one is designed for different scenarios, but their purpose is the same, which is to check the equality of the two objects. In this post, we will be focusing on the following three methods, out of the four, which are.

  1. virtual Object.Equals
  2. static Object.Equals
  3. static Object.ReferenceEquals

Virtual Object.Equals() Method

As we discussed in the previous post as well, in .NET, there are a number of ways to compare equality, but the most fundamental way .NET provides for this purpose is the virtual Object.Equals() method, defined in the System.Object type.

To see this method in action, we will create a class, which represents a kind of person. The only thing our class contains is a string field containing the name of the person and a constructor that enforces setting the value of the name field.

public class Person
{
    private string _name;
    public string Name   
    {   
        get   
        {   
            return _name;   
        }   
    }
    public Person(string name)
    {
        _name = name;
    }

    public override string ToString()
    {
        return _name;
    }
}  

Now, we will write the Main method to use this type.

static void Main(String[] args)
{
    Person p1 = new Person("Ehsan Sajjad");
    Person p2 = new Person("Ahsan Sajjad");
    Console.WriteLine(p1.Equals(p2));
}

As you can see, in the Main method, we are creating the two instances of the Person class that passes the different value as a parameter to the constructor and then on the next line, we are checking for equality, using the Object. Equals method. The equal method returns a Boolean and returns true if both the items are equal and false if they are not equal. We are writing the result of the equality comparison on the console to see what it outputs.

You would hope from the code that the two instances are not equal as “Ehsan Sajjad” and “Ahsan Sajjad” are not equal. They have different sequences of characters and of course, if we run the code, we will see false as an output on the console. Hence, Equals() appears to be working right here and if you notice, we didn’t have to do anything in our Person class definition to achieve this. The Equals method is provided by System. Object. Hence, it is automatically available on all the types, because all the types are ultimately derived from System. Object

By the way, I suspect some of you may be looking at this code and thinking that the line p1.Equals(p2) is not what we write normally for checking equality. If we want to check for equality, we just write this p1 == p2, but the point here is; that we need to understand how equality works in .NET and the starting point for that is the Equals method.

== Operator and Equals Method

If you write == in your code, you are using a C# equality operator and that is a very nice syntactical convenience C# language provides to make the code more readable, but it is not really a part of the .NET framework. .NET has no concept of the operators as it works they work with the methods. If you want to understand how equality works in .NET, then we need to start with the things that the .NET framework understands. Hence, we will only be using .NET methods in this post for most of the code. Therefore, you can see how it works, which means some of the code you will see may look unnatural, but we will discuss the == (c# equality operator) in another post.

Now, let’s get back to the fun part i.e. the code. We will declare another instance of Person in the Main program

This new instance p3 also passes the same value in the constructor as p1 which is “Ehsan Sajjad”, so what do you think will happen if we try to compare p1 with p3, using the Equals method? Let’s try and see what happens.

static void Main(String[] args)
{
    Person p1 = new Person("Ehsan Sajjad");
    Person p2 = new Person("Ahsan Sajjad");
    Person p3 = new Person("Ehsan Sajjad");
    
    Console.WriteLine(p1.Equals(p3));
}

This also returns false. These two instances p1 and p3 are not equal and the reason is the base Object. The equals method evaluates reference equality. Its implementation tests whether the two variables refer to the same instance. In this case, it is obvious to us that p1 and p3 have exactly the same value. Both instances contain the same data but the Equals method does not care about it. It cares only about whether they are the same or different instances. Hence, it returns false, telling us that they are not equal.

As we discussed earlier in this post and in the previous post as well, Equals is a virtual method in Object type, which means that we can override it. If we want the Equals method to compare the values of the Person instances, we can override the Equals method and can define our own implementation for how to compare two Person instances for equality. There is nothing unusual in this, as Equals is a normal virtual method. We are not going to override it yet though if you want to stick to good coding practices, you need to do a few other things when you override Object. Equals method. For this post, we will just stick to what Microsoft has given us out of the box.

Equals method implementation for string

There are a couple of reference types for which Microsoft has overridden the Object.The equals method, in order to compare the values instead of the references, is probably the most well-known of these, and certainly the one that’s most important to be aware of is String. We will examine this with a small program to demonstrate it.

static void Main(String[] args)
{
    string s1 = "Ehsan Sajjad";
    string s2 = string.Copy(s1);

    Console.WriteLine(s1.Equals((object)s2));
}

In this program, we initialize a string and store its reference in s1. We will also create another variable s2, which contains the same value, but we use initializer s2, by creating a copy of the value s1, which has a string. Copy method’s name is pretty descriptive. It creates and returns the copy of the string and then we use the Equals method to compare them.

You can see that we are casting the argument of the Equals method explicitly to the object type, which obviously you would not want to do in the production code. The reason for doing this is to make sure that the implementation to override of Object.Equals() is called, as a string defines multiple Equals method, and one of them is strongly required to type a string i.e. it takes a string as a parameter. If we didn’t cast it to the object, then the compiler would have considered the strongly typed method a better parameter when resolving overloads and would have called that one, which is obviously better, when we are normally programming and both the methods will actually do the same thing, but we explicitly wanted to show how the object. Equals override behaves. Hence, we need to cast the parameter to the object to tell the compiler to avoid strongly typed overload and use the object type override.

If we run the code, given above, will see that it returns true. The override provided by Microsoft for the string type compares the contents of the two string instances to check whether they contain exactly the same characters in the same sequence and returns true if they are, otherwise, it returns false, even if they are not the same instance.

There are not many Microsoft-defined reference types; for which Microsoft has overridden the Equals method to compare the values. Apart from the String type, two other types that you must be aware of are Delegate and Tuple.

Equals Method and Value Types

Now, we will see how the Equals method works for the value types. We will be using the same example that we used at the start of the post (Person class ), but we will change it to struct instead of the class to see the behavior in the case of the value type.

public struct Person
{
    private string _name;
    public string Name
    {
        get
        {
            return _name;
        }
    }
    public Person(string name)
    {
        _name = name;
    }
    public override string ToString()
    {
        return _name;
    }
}

What do you think will happen now, if we run the same program again, as we know struct is stored on the stack? They don’t have the references normally; unless we box them. This is the reason why they are called value types and not reference types.

static void Main(String[] args)
{
    Person p1 = new Person("Ehsan Sajjad");
    Person p2 = new Person("Ahsan Sajjad");
    Person p3 = new Person("Ehsan Sajjad");
    
    Console.WriteLine(p1.Equals(p2));
    Console.WriteLine(p1.Equals(p3));
}

Hence, as we know the implementation of Object. Equals are required to do the reference comparison in the case of the reference types, but in this case, you might think that comparing the references does not make sense as struct is a value type.

So let’s run the program and see what it prints on the console.

Console

You can see, the result is different this time. For the second case; it is saying that both instances are equal. It is exactly the result you would expect; if you were to compare the values of both p1 and p3 instances of the person to see if they were equal, and that is actually happening in this case, but if we look at the Person type definition, we have not added any code to override the Equals method of Object class, which means there is nothing written in this type to tell the framework to compare the values of the instances of Person type to see if they are equal.

.NET already knows all that. It knows how to do this. The .NET framework has figured out without any effort from us how to tell if p1 and p3 have equal values or not, how is that happening? What is actually happening? As you already know, all struct types inherit from System.ValueType, which is ultimately derived from System.Object.

System.ValueType itself overrides the System. Object Equals method and what the override does is that it traverses all the fields in the value type and calls Equals against each one; until it either finds any field value that is different or all the fields are traversed. If all the fields turn out to be equal, then it figures out that these two value-type instances are equal. In other words, the value types override the Equals method implementation and say that both the instances are equal; if every field in them has the same value; which is quite sensible. In the above example, our Person type has only one field in it; which is the backing field for the Name property, which is of the typed string and we already know that calling Equals on the string compares the values and the above results of our program prove what we are stating here. That’s how .NET provides the behavior of the Equals method for the value types very nicely.

Equals method overhead for Value type

Unfortunately, this convenience provided by the .NET framework comes with a price. The Way System.ValueTypeequals implementation works by using reflection. Of course, it has to, if we think about it. As System.ValueType is a base type and it does not know how you will derive from it. Hence, the only way to find out what fields are in our defined type (in this case Person) is to do it using Reflection, which means that performance would be bad.

Recommended Approach with Value Types

The recommended way is to override the Equals method for your own value types; which we will see later; and how to provide that in a way that it runs faster. In fact, you will see that Microsoft has done that for many of the built-in value types that come with the framework which we use every day in our code.

Static Equals Method

There is one problem when checking if equality is being done using the virtual Equals method. What will happen if one or both of the references you want to compare is null. Let’s see what happens when we call the Equals method with null as an argument. Let’s modify the existing example for it:

static void Main(String[] args)
{
    Person p1 = new Person("Ehsan Sajjad");
    Console.WriteLine(p1.Equals(null));
}

If we compile this and run, it returns false and it makes perfect sense because it is obvious that null is not equal to a non-null instance and this is the principle of Equality in .NET, that Null should never be evaluated as equal to a Non-Null value.

Now, let’s make it vice versa to see what will happen if the p1 variable is null, then we have a problem. Consider, if we don’t have this hardcoded instance of creation code. Instead of it, this variable is passed as a parameter from some client code, which uses this assembly and we don’t know if either of the values is null or not.

If p1 is passed as null, executing the Equals method, a call will throw a Null Reference Exception, because you cannot call instance methods against the null instances.

The Static Equals method was designed to address this problem. Hence, we can use this method, if we are not aware if one of the objects could be null or not, and we can use it in the way shown below.

Console.WriteLine(object.Equals(p1, null));

Now, we are good to go without worrying about if either of the instance references is null or not. You can test it by running with both scenarios and you will see it works fine and of course, it would return false; if one of the reference variables is null.

Some of you may be wondering what would happen if both the arguments passed to the Static Equals method are null and if you add the following line to test it.

Console.WriteLine(object.Equals(null, null));

You will see that it returns true in this case. In .NET, null is always equal to null, so testing whether null is equal to null should always be evaluated to be true.

If we dig into the implementation of the Static Equals method, we will find that it is a very simple implementation. Following is the logic of it, if you look into the source code of Object type.

public static bool Equals(object x, object y)
{
    if (x == y) // Reference equality only; overloaded operators are ignored
    {
        return true;
    }
    if (x == null || y == null) // Again, reference checks
    {
        return false;
    }
    return x.Equals(y); // Safe as we know x != null.
}

It first checks if both the parameters refer to the same instance i.e. what the == operator will do. This check will evaluate to be true; causing the method to return true if both the parameters are null. The next if block will return false if one of the parameters is null and another one is not. Finally, if control reaches the else block, then we know that both the parameters point to some instance. Hence, we will just call the virtual Equals method.

This means that the static Equals method will always give the same result as the virtual method, except that it checks for null first, as a static method calls the virtual method. If we override the virtual Equals method, our override will automatically be picked by the static method, which is important, as we want both static and virtual methods to behave consistently.

ReferenceEquals Method

ReferenceEquals serves a slightly different purpose from the two Equals methods which we have discussed above. It exists for those situations where we specifically want to determine whether the two variables refer to the same instance or not. You may have a question in mind that if the Equals method also checks the reference equality then what is the need for a separate method?

These two methods do check reference equality, but they are not guaranteed to do so, because the virtual Equals method can be overridden to compare the values of the instance and not the reference.

Hence, ReferenceEquals will give the same result as Equals for the types that have not overridden the Equals method. For example, take the Person class example, which we used above. It’s possible to have different results for the types that have overridden the equals method. For Example, the String class.

Let’s modify the string class example that we used earlier in this post to demonstrate this.

static void Main(string[] args)
{
    string s1 = "Ehsan Sajjad";
    string s2 = string.Copy(s1);

    Console.WriteLine(s1.Equals((object)s2));
    Console.WriteLine(ReferenceEquals(s1, s2));
}

If we run this example, we will see that the first Equals call returns true just as shown before, but the ReferenceEquals method call returns false why is that?

It is telling us that both the string variables are different instances, even though they contain the same data recall what we discussed in the previous post String type overrides the Equals method to compare the value of two instances and not the reference.

You know that in C# static methods cannot be overridden, which means you can never change the behavior of the ReferenceEquals method, which makes sense because it always needs to do the reference comparison.

Summary

  • We learned how .NET provides the types of equality implementation out of the box
  • We saw that a few methods are defined by the .NET framework on the Object class, which is available for all the types.
  • By default, the virtual Object.The equals method does reference equality for the reference types and the value equality for the value types by using reflection, which is a performance overhead for the value types.
  • Any type can override an Object. Equals method to change the logic of how it checks for equality e.g. String, Delegate, and Tuple do this to provide the value equality, even though these are the reference types.
  • Objects provide a static Equals method, which can be used when there is a chance that one or both of the parameters can be null, other than that, it behaves identically to the virtual Object. Equals method.
  • There is also a static ReferenceEquals method, which provides a guaranteed way to check for reference equality.