Common misunderstandings of Equals method in C

  • 2021-08-21 21:10:30
  • OfStack

Many C # textbooks emphasize the concept of object equality. As we all know, there are two kinds of equivalents in the world of C #. One is logical equivalence: Two objects are said to be logically equivalent if they logically represent the same value. The other is reference equivalence: If two references point to the same object instance, they are said to have reference equivalence.

As we all know, the Object type has an instance method named Equals that can be used to determine whether two objects are equal. The default implementation of Equals for Object compares the reference equivalence of two objects. ValueTpye, a derivative of Object, overrides the Equals method, which compares the logical equivalence of two objects.

That is, in C #, the default Equals version of reference types focuses on reference equivalence, while value types focus on logical equivalence. Of course, this does not always meet our requirements. So whenever we care more about the logical equivalence of reference types, we should override the Equals method.

A well-known example of overriding an Equals method of a reference type to change its default comparison is the String class. When we write code like "string1.Equals (string2)", we compare not whether the references to string1 and string2 refer to the same instance (reference equivalence), but whether the sequences of characters contained in string1 and string2 are the same (logical equivalence).

Myth 1: The Equals method and operator== have the same default behavior.

For a reference type, if it is not overloaded with the == operator and its parent type does not override the Equals method, the reference type Equals method and operator == have the same default behavior, that is, they compare the reference equivalence of objects. However, this is not the case at all for value types! Because if you don't overload operator== for the custom value type, you can't write the code "myStruct1 = = myStruct2", otherwise you will get a compilation error because the value type does not have a default implementation of the equality operator overload.

Myth 2: The default implementation of the Equals method of the custom class will automatically call the operator== method, or the default implementation of the operator== method will automatically call the Equals method.

It is often said that a certain type is a reference type, so the default implementation of its Equals method will automatically call the operator = = method. This statement is totally unreasonable. As mentioned above, the default implementation of the reference type Equals method comes from Object, and the default implementation of the value type comes from TypeValue, even if they use the == operator, they use an overloaded version of Object or TypeValue.

In principle, as long as we don't override the Equals method of a class, it inherits the implementation of its parent class, which has no chance to use the operator overload of its subtype. Again, the Equals method is not called automatically as long as we do not call it in the = = operator overload of a class.

Myth 3: The default Equals implementation of a value type compares two objects bit by bit.

Some people think that the default implementation of Equals for value types is to compare the bit representations of two objects in memory, that is, if all binary bits are equal, the two objects are "equivalent". This is inaccurate. Because the default implementation of Equals for a real value type is to call the Equals method for each field of the value type, all fields can be equal if their Equals methods return true. Look at an example:


class MyClass 
{ 
public override bool Equals(object obj) 
{ 
Console.WriteLine("MyClass Adj. Equals Method has been called. "); 
return true; 
} 
} 
struct MyStruct 
{ 
public MyClass Filed; 
} 
class Program 
{ 
static void Main(string[] args) 
{ 
MyStruct a; 
MyStruct b; 
a.Filed = new MyClass(); 
b.Filed = new MyClass(); 
Console.WriteLine(a.Equals(b)); 
} 
} 

Obviously, a and b have completely different binary bit representations. But the final print result is:


MyClass Adj. Equals Method has been called.  
True 

This shows that the default implementation of a value type determines whether two objects are equal by calling the field's Equals method, not by comparing whether their binary bits are 1.

Myth 4: Equals is a very basic, very common method, so its default implementation has no performance problems.

For reference types, the default implementation of Equals is very simple, just need to judge whether two references are of the same type and whether two references point to the same block of memory. So there is no problem with its performance. But for value types, the task of Equals is not so simple. It needs to compare all the fields of the two objects, that is, call Equals of the field type field by field.

Because in ValueType (where the value type Equals method is implemented by default), it is impossible to know which fields are contained in all its subtypes, Equals of ValueType needs to use reflection technology in order to call Equals methods of subtype fields. As you may have seen, reflection is not a performance-friendly technique, so the Equals method of value types is not efficient. This is why Microsoft recommends that we override the Equals method for custom value types.

Through the introduction of Equals in this article, I hope it will be helpful to you.


Related articles: