Comparing Objects by Value. Part 4: Inheritance & Comparison Operators

In the previous article, we analyzed how to compare objects by value on a particular example with the Person class that includes:

Each method returns the same result:

In addition, I would like to add that each method should be commutative:

x.Equals(y) returns the same result as y.Equals(x)

Thus, a client-side code may compare objects by using any methods. In any case, the result will be deterministic.

Still, it is necessary to determine how we may ensure determinacy of the result when implementing static methods and comparison operators in the case of inheritance. Additionally, we should take into account that static methods and operators have no polymorphic behavior.

To determine it, I will use the following example with the Person class:

Also, I will create the derived class PersonEx:

As you can see, we have added the MiddleName property. It means that it is necessary:

  • To implement the IEquatable(Of PersonEx) interface.
  • To implement the PersonEx.Equals(Person) method, overriding the inherited method Person.Equals(Person) and casting the object of the Person type to the object of the PersonEx type.
    Note: the latter one has been declared as virtual to have inheritance be possible.

Otherwise, objects comparison, where all key fields (except MiddleName) are equal, will return the result ‘objects are equal’, which is incorrect.

It should be noted that:

  • The implementation of the PersonEx.Equals(PersonEx) method is identical to the one of the Person.Equals(Person) method.
  • The implementation of the PersonEx.Equals(Person) method is identical to the one of the Person.Equals(Object) method.
  • The implementation of the static protected method EqualsHelper(PersonEx, PersonEx) is identical to the one of the EqualsHelper(Person, Person) method.

Then, we implement the PersonEx.Equals(Object) method that overrides the inherited method Equals(Object) and represents a call of the PersonEx.Equals(PersonEx) method with casting the specified object to the PersonEx type with the as operator.

The implementation of the PersonEx.Equals(Object) method is not a must. If we have not done it, but called the Equals(Object) method by a client-side code, the inherited method Person.Equals(Object) would be used that calls the virtual method PersonEx.Equals(Person) casting to the PersonEx.Equals(PersonEx) method.

However, the PersonEx.Equals(Object) method is implemented for the completeness of the code and its faster performance by minimizing the number of type casts and intermediate calls of methods.

Now, it does not matter what method for the object of the PersonEx class we will call: Equals(PersonEx), Equals(Person), or Equals(object); as the same objects will return the same result.

Polymorphism allows such a behavior.

In addition, we have implemented the static method PersonEx.Equals(PersonEx, PersonEx), as well as its corresponding comparison operators PersonEx.==(PersonEx, PersonEx) and PersonEx.!=(PersonEx, PersonEx) in the PersonEx class.

Using the PersonEx.Equals(PersonEx, PersonEx) method, as well as the PersonEx.==(PersonEx, PersonEx) and PersonEx.!=(PersonEx, PersonEx) operators for the same objects, will return the same result as using the Equals instance methods of the PersonEx type.

The more we move on, the more interesting it becomes.

As you can see, the PersonEx class has inherited the static method Equals(Person, Person) and corresponding comparison operators ==(Person, Person) and  !=(Person, Person) from the Person class.

I wonder, what result the following code would return.

Although the Equals(Person, Person) method and comparison operators ==(Person, Person) and !=(Person, Person) are static, they will always return the same result when calling for the Equals(PersonEx, PersonEx) method, the ==(PersonEx, PersonEx) and !=(PersonEx, PersonEx) operators, or any instance virtual Equals methods.

Thus, to get such a polymorphic behavior, it is necessary to implement the Equals static methods and the “==” and “!=” comparison operators using the instance virtual Equals method.

Furthermore, the implementation of the Equals(PersonEx, PersonEx) and the ==(PersonEx, PersonEx) and !=(PersonEx, PersonEx) operators in the PersonEx class is optional. The same is applied for the PersonEx.Equals(Object) method.

The Equals(PersonEx, PersonEx) methods, as well as the ==(PersonEx, PersonEx) and !=(PersonEx, PersonEx) operators are implemented for the completeness of the code and its faster performance by minimizing the number of type casts and intermediate calls of methods.

The only difference in the polymorphism of the Equals static methods and the “==” and “!=” operators is that if we cast two objects of the Person or PersonEx type to the Object type, then it will be possible to compare objects by reference with the == and != operators, and by value – using the Object.Equals(Object, Object) method. But, it depends on the platform design.

In my further publication, we will consider peculiarities of comparison by value for the objects and structure instances. In addition, we will analyze cases when it is a good idea to implement comparison of objects by value and how it can be achieved.

Also read:

Comparing Objects by Value. Part 1: Beginning

Comparing Objects by Value. Part 2: Implementation Notes of the Equals Method

Comparing Objects by Value. Part 3: Type-specific Equals and Equality Operators

Comparing Objects by Value. Part 5: Structure Equality Issue

Comparing Objects by Value. Part 6: Structure Equality Implementation

Andrey Sergeev

Andrey Sergeev

Andrey Sergeev is experienced DDD, C# and .NET developer, and is interested in Java and Ruby stacks.
For last ten years, Andrey developed the multi-tier traceability system and business analysis systems.
In present, Andrey is developing the famous mobile fitness application.
Andrey Sergeev