Background
This article is in the continuation of a series of articles regarding how Equality works in .Net, the purpose is to have the developers have a more clear understanding on how .Net handles equality for different types. You may want to read the previous post as well,
In the
previous post (i.e. Part 4), we compared the result of an equality comparison of a value type, using == operator and Object.Equals method and the result was the same but the mechanism for evaluating the equality is different, as the IL generated for Object.Equals and == operator was different, which means that == operator does not call Object.Equals behind the scenes, but it uses CPU’s registers to determine if the two value type variables are equal or not.
Introduction
I hope, after reading the previous posts, you have now a better understanding, how the C# equality operator works for the primitive types like int, float, double, etc. In this post, we will focus, on how the C# equality operator and Equals method behave for the reference types. They are either framework class library types or user-defined custom types.
== Operator and Reference Types
If you recall from the previous post, we saw an example, using the reference types for the equality, which it checks for the reference equality. Now, we will modify the same example to see that an equality operator compiles to what is the case of the reference types.
- class Program
- {
- static void Main(String[] args) {
- Person p1 = new Person();
- p1.Name = "Ehsan Sajjad";
- Person p2 = new Person();
- p2.Name = "Ehsan Sajjad";
- Console.WriteLine(p1.Equals(p2));
- Console.WriteLine(p1 == p2);
- Console.ReadKey();
- }
- }
Now, we will test, using both these methods of checking equality i.e. equality operator of C# and Equals method of the Object type. If we run this example, we will see false printed on the console two times, which was the expected result as Person is a reference type and those are the two different instances of the person with the difference in a memory reference.
From the output, we can easily infer, both the equality operator and Equals method check for the reference equality for the reference types, and that’s actually happening. Hence, the equality operator also checks the reference equality and not the value equality for the reference types, similar to the Equals method.
What Happens Behind the Scene
Now, let’s examine the IL code, generated for this example. For doing that, open the Visual Studio commands prompt. To open it, go to Start Menu >> All Programs >> Microsoft Visual Studio >> Visual Studio Tools>> Developer Command Prompt.
Type ildasm on the command prompt and this will launch the ildasm, which is used to look at the IL code, contained in an assembly. It is installed automatically when you install Visual Studio. Thus, you don’t need to do anything to install it.
Browse the folder, where your executable exists and open it, using
the File menu. This will bring up the IL code of your executable.
IL code for C# code, given above, looks like:
If we see IL code for p1.Equals(p2), there are no surprises. It compares the equality by calling the virtual Equals method of Object, and the method signatures are pretty clear, as they say, it requires an object, so this is actually a virtual call to Object.Equals method, this is the best type’s method match that C# compiler picked.
Now, if we look at IL code, generated for an equality operator comparison of the objects. It is exactly the same instruction used, which we saw for the integer example in the previous part. It is not calling any method to do the comparison. It is just loading both the arguments to the evaluation stack and doing a CEQ, which is a dedicated IL instruction to check for equality, probably using the CPU’s hardware.
You might be thinking, how does that achieve the reference equality? We know that Person is a class, which is a reference type, which means whatever the variable of type Person contains is the address in the memory of where the Person object is on the managed heap.
Both the arguments p1 and p2 are holding the memory addresses and you know the addresses in the memory are just the numbers, which means that they can be compared, using the CEQ statement for equality, like the integers, you declare in the code. In fact, this CEQ compares the addresses to see, if they are equal. In other words, whether the reference is pointing to the same memory address is the reference equality.
Summary
- We saw that == operator and Object.Equals method call both works differently behind the scenes, which we can verify by inspecting the IL code generated.
- We saw that for the Reference types as well as using the == operator gives us the same result, as calling Object.Equals, but the underlying mechanism of == operator is different in IL, as compared to Object.Equals, which is that it does not use the Object.Equals. Instead, it uses CEQ instruction, which does the comparison of the memory addresses.