Share via


.NET: Understand Equality for Value Types and Reference Types

Introduction

In this post, we will see how smartly .NET handles equality and comparison out of the box. This means that we will be able to understand how .NET handles equality for both value types and reference types and we will be seeing different ways of comparing two objects for equality,

There are 4 methods in Object class which are provided by .NET Framework for the purpose of equality checking. Each one is designed for different scenarios but their purpose is the same which is to check equality of two objects. In this post, we will be focusing on the following three methods out of the four which are:

We will start by looking in detail at the virtual Object.Equals method. This provides the most important mechanism for equality checking in .NET, since it is the means by which any type can tell what equality means for itself. We will see how out of the box this method gives you reference equality for most reference types but value equality for all value types.

We will also compare it with the static method Object.Equals() which is of same name which is more robust when the scenario is that instances to be checked for equality can be null.

We will also see how we can guarantee that the equality check is done on the reference of instances not the values of the instances using the static Object.ReferenceEquals method.

After reading this post hopefully we will have a good understanding what equality means in .NET.

Virtual Object.Equals() Method

As we discussed above as well  that 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 to set 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 we can see in Main, we are creating two instances of Person class passing different value as 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, it returns true if both items are equals 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 sequence of characters, and of course if we run the code, we will see false as output on the console. So 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 so it is automatically available on all types, because all types ultimately derive from System.Object.

By the way, Some readers might 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 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 the 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 part of the .NET Framework. .NET has no concept of operators, it works with methods. If we want to understand how equality works in .NET, then we need to start with the things that .NET Framework understands. So we will only be using .NET methods in this post for most of the code.

Now, let’s get back to 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.Equals method evaluates reference equality, its implementation tests whether two variables refers to the same instance, in this case, it is obvious to us and that p1 and p3 have exactly the same value, both instances contains the same data but Equals method does not care about that, it cares only about that they are same or different instances, so 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, 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 few other things when you override Object.Equals method, we will see later how to do that, for this post we will just stick to what Microsoft has given us out of the box.

Equals Method Implementation for String

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

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, then we create another variable s2 which contains the same value but we initialize s2 by creating a copy of the value s1 has, string.Copy method’s name is pretty descriptive, it creates and returns the copy of the string and then we are using Equals method to compare them.

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

If we run the above code, we will see that it returns true. The override provided by Microsoft for 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 returns false, even if they are not the same instance.

There are not many Microsoft defined reference types for which Microsoft has overridden Equals method to compare values, apart from String type, two others types that you must be aware of are Delegate and Tuple, calling Equals on these two will also compare the values. These are the exceptional ones. All other reference types Equals will always do Reference equality check.

Equals Method and Value Types

Now we will see how Equals method works for 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 class for seeing the behavior in case of 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 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 references normally unless we box them, that’s why they are called value type not reference type.

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));
}

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

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

You can see that this time the result is different, 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 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 for overriding the Equals method of Object class, which means there is nothing written in this type to tell the framework how to compare the values of instances of Person type to see if they are equal.

.NET already knows all that, it knows how to do that, .NET Framework has figured out without any effort from us how to tell p1 and p3 have equal values or not, how that is happening. What is actually happening is that as you may already know, all struct types inherits from System.ValueType which ultimately derives 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 call Equals against each one until it either finds any field value that is different or all 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, value types override the Equals method implementation and says that both 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 type string and we already know that calling Equals on string compares values and the above results of our program proves what we are stating here. That’s how .NET provides the behavior of Equals method for value types very nicely.

Performance Overhead for Value Types

Unfortunately, this convenience provided by .NET Framework comes with a price. The way System.ValueType Equals implementation works is 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 about how you will derive from it, so the only way to find out what fields in our defined type (in this case Person) has is to do it using Reflection which means that performance would be bad.

The recommended way is to override the Equals method for your own value types. We will see later 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 comes with the framework which we use every day in our code.

Static Equals Method

There is one problem when checking for equality 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 Equals method with null as argument. Let’s modify the existing example for that:

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 should and makes perfect sense because it is obvious that null is not equal to non-null instance and this is the principle of Equality in .NET that Null should never evaluate as equal to 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 that we don’t have this hardcoded instance creation code. Instead of that, this variable is passed as parameter from some client code which uses this assembly and we don’t know if either of the values is null. If p1 is passed as null, executing the Equals method call will throw a Null Reference Exception, because you cannot call instance methods against null instances. The Static Equals method was designed to address this problem, so we can use this method if we are not aware if one of the objects could be null or not, we can use it this way:

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

Now we are good to go without worrying about whether either of the instance references is null, 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 have happened if both the arguments passed to Static Equals method are null, if you add the following line to test that:

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 evaluate to true.

If we dig into the implementation Static Equals method, we will find that it is 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.
}

If first checks if both parameters refer to the same instance i.e. what == operator will do, this check will evaluate to true causing 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 other one is not, finally if control reaches the else block, then we know that both parameters point to some instance, so 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 static method call the virtual method, if we override the virtual Equals method, our override will automatically be picked by the static method, that’s important as we want both static virtual methods to behave consistently.

ReferenceEquals Method

ReferenceEquals serves a slightly different purpose from the two Equals method 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. You may have question in mind that Equals method also checks reference equality, then why a separate method.

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

So, ReferenceEquals will give the same result as Equals for types that have not overridden the Equals method. For example, take the Person class example which we used above. But it’s possible to have different results for 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 before, but the ReferenceEquals method call returns false, and why is that?

It is telling that both string variables are different instances even though they contain the same data as String type overrides Equals method to compare the value of two instances, not the reference.

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

Summary

  • We learned how .NET provides the types equality implementation out of the box. 
  • We saw that few methods are defined by the .NET Framework on the Object class which are available for all types. 
  • By default, the virtual Object.Equals method does reference equality for reference types and value equality for value types by using reflection which is a performance overhead for value types. 
  • Any type can override Object.Equals method to change the logic of how it checks for equality, e.g., String, Delegate and Tuple do this for providing value equality, even though these are reference types. 
  • Object provides 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 identical to the virtual Object.Equals method. 
  • There is also a static ReferenceEquals method which provides a guaranteed way to check for reference equality.

See Also