Each of the comparison methods for any one and the same pair of objects returns the same result:
Person p1 = new Person("John", "Smith", new DateTime(1990, 1, 1)); Person p2 = new Person("John", "Smith", new DateTime(1990, 1, 1)); //Person p2 = new Person("Robert", "Smith", new DateTime(1991, 1, 1)); object o1 = p1; object o2 = p2; bool isSamePerson; isSamePerson = o1.Equals(o2); isSamePerson = p1.Equals(p2); isSamePerson = object.Equals(o1, o2); isSamePerson = Person.Equals(p1, p2); isSamePerson = p1 == p2; isSamePerson = !(p1 == p2);
At the same time, each of the comparison methods is commutative:
x.Equals (y) returns the same result as y.Equals (x), etc.
Thus, the client code can compare objects in any way - the result of the comparison will be determined.
How exactly is the determinism of the result ensured when implementing static methods and comparison operators in the case of inheritance - taking into account the fact that static methods and operators do not have polymorphic behavior.
For clarity, we present the Person class from the previous publication :
using System; namespace HelloEquatable { public class Person : IEquatable<Person> { protected static int GetHashCodeHelper(int[] subCodes) { if ((object)subCodes == null || subCodes.Length == 0) return 0; int result = subCodes[0]; for (int i = 1; i < subCodes.Length; i++) result = unchecked(result * 397) ^ subCodes[i]; return result; } protected static string NormalizeName(string name) => name?.Trim() ?? string.Empty; protected static DateTime? NormalizeDate(DateTime? date) => date?.Date; public string FirstName { get; } public string LastName { get; } public DateTime? BirthDate { get; } public Person(string firstName, string lastName, DateTime? birthDate) { this.FirstName = NormalizeName(firstName); this.LastName = NormalizeName(lastName); this.BirthDate = NormalizeDate(birthDate); } public override int GetHashCode() => GetHashCodeHelper( new int[] { this.FirstName.GetHashCode(), this.LastName.GetHashCode(), this.BirthDate.GetHashCode() } ); protected static bool EqualsHelper(Person first, Person second) => first.BirthDate == second.BirthDate && first.FirstName == second.FirstName && first.LastName == second.LastName; public virtual bool Equals(Person other) { //if ((object)this == null) // throw new InvalidOperationException("This is null."); if ((object)this == (object)other) return true; if ((object)other == null) return false; if (this.GetType() != other.GetType()) return false; return EqualsHelper(this, other); } public override bool Equals(object obj) => this.Equals(obj as Person); public static bool Equals(Person first, Person second) => first?.Equals(second) ?? (object)first == (object)second; public static bool operator ==(Person first, Person second) => Equals(first, second); public static bool operator !=(Person first, Person second) => !Equals(first, second); } }
using System; namespace HelloEquatable { public class PersonEx : Person, IEquatable<PersonEx> { public string MiddleName { get; } public PersonEx( string firstName, string middleName, string lastName, DateTime? birthDate ) : base(firstName, lastName, birthDate) { this.MiddleName = NormalizeName(middleName); } public override int GetHashCode() => GetHashCodeHelper( new int[] { base.GetHashCode(), this.MiddleName.GetHashCode() } ); protected static bool EqualsHelper(PersonEx first, PersonEx second) => EqualsHelper((Person)first, (Person)second) && first.MiddleName == second.MiddleName; public virtual bool Equals(PersonEx other) { //if ((object)this == null) // throw new InvalidOperationException("This is null."); if ((object)this == (object)other) return true; if ((object)other == null) return false; if (this.GetType() != other.GetType()) return false; return EqualsHelper(this, other); } public override bool Equals(Person other) => this.Equals(other as PersonEx); // Optional overloadings: public override bool Equals(object obj) => this.Equals(obj as PersonEx); public static bool Equals(PersonEx first, PersonEx second) => first?.Equals(second) ?? (object)first == (object)second; public static bool operator ==(PersonEx first, PersonEx second) => Equals(first, second); public static bool operator !=(PersonEx first, PersonEx second) => !Equals(first, second); } }
(Otherwise, a comparison of objects that have all key fields equal, except MiddleName, will return the result of "objects are equal," which is not correct from an objective point of view.)
Wherein:
It is worth noting that the implementation of PersonEx.Equals (Object) is not mandatory, since in case of its absence and calling the client code of the Equals (Object) method, the inherited method Person.Equals (Object) would be called, which internally calls the virtual method PersonEx.Equals (Person), resulting in a call to PersonEx.Equals (PersonEx).
However, the PersonEx.Equals (Object) method is implemented for code "completeness" and greater speed (by minimizing the number of type conversions and intermediate method calls).
Now, no matter what method we called to the object of the class PersonEx:
Equals (PersonEx), Equals (Person), Equals (object),
for any one and the same pair of objects, the same result will be returned (when changing operands in some places, the same result will also be returned).
Polymorphism allows for this behavior.
Using the PersonEx.Equals method (PersonEx, PersonEx) or the PersonEx. == (PersonEx, PersonEx) and PersonEx.! = (PersonEx, PersonEx) operators for any one and the same pair of objects will give the same result as using the Equals instance methods. class PersonEx.
The PersonEx class "inherited" from the Person class the static Equals method (Person, Person) and the corresponding comparison operators == (Person, Person) and! = (Person, Person).
bool isSamePerson; PersonEx pex1 = new PersonEx("John", "Teddy", "Smith", new DateTime(1990, 1, 1)); PersonEx pex2 = new PersonEx("John", "Bobby", "Smith", new DateTime(1990, 1, 1)); //PersonEx pex2 = new PersonEx("John", "Teddy", "Smith", new DateTime(1990, 1, 1)); Person p1 = pex1; Person p2 = pex2; isSamePerson = Person.Equals(pex1, pex2); isSamePerson = PersonEx.Equals(p1, p2); isSamePerson = pex1 == pex2; isSamePerson = p1 == p2;
Although the Equals method (Person, Person) and the == (Person, Person) and! = (Person, Person) comparison operators are static, the result will always be the same as when you called the Equals method (PersonEx, PersonEx ), operators == (PersonEx, PersonEx) and! = (PersonEx, PersonEx), or any of the instance Equals virtual methods.
Moreover, the implementation of the Equals method (PersonEx, PersonEx) and == (PersonEx, PersonEx) and! = (PersonEx, PersonEx) operators in the PersonEx class, as well as the PersonEx.Equals (Object) method, is optional.
The Equals method (PersonEx, PersonEx) and operators == (PersonEx, PersonEx) and! = (PersonEx, PersonEx) are implemented for code “completeness” and faster performance (by minimizing the number of type conversions and intermediate method calls).
The only obscure point in the "polymorphism" of static Equals, "==" and "! =" Is that if two objects of type Person or PersonEx are reduced to type object , then the comparison of objects with the help of == and ! = Operators will be done by reference , and using the Object.Equals (Object, Object) method - by value. But this is a “by design” platform.
Source: https://habr.com/ru/post/315258/
All Articles