値-4、または継承と等価演算子によるオブジェクトの比較について

以前の出版物では、Personクラスを例として使用して、 .NETプラットフォームの値でオブジェクトを比較するための実装オプションを受け取りました。





オブジェクトの任意の同じペアの各比較メソッドは、同じ結果を返します。







コード例
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);
      
      





さらに、各比較方法は可換です:

x.Equals(y)は、y.Equals(x)などと同じ結果を返します。







したがって、クライアントコードはオブジェクトを任意の方法で比較できます。比較の結果は確定的です。







ただし、この問題には開示が必要です。



静的メソッドと演算子にポリモーフィックな振る舞いがないという事実を考慮して、継承の場合に静的メソッドと比較演算子を実装するときに、結果の決定性はどのくらい正確に保証されますか?







明確にするために、 以前の出版物からPersonクラスを提供します。







クラスPerson
 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); } }
      
      





PersonEx子クラスを作成します。



クラスPersonEx
 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); } }
      
      





MiddleNameのもう1つの重要なプロパティが後継クラスに登場しました。 したがって、最初に必要なこと:





(それ以外の場合、MiddleNameを除くすべてのキーフィールドが等しいオブジェクトを比較すると、「オブジェクトが等しい」という結果が返されますが、これはサブジェクトの観点からは正しくありません。)







この場合:









次に、継承されたEquals(Object)メソッドをオーバーライドするPersonEx.Equals(Object)メソッドが実装され、As演算子を使用して着信オブジェクトをPersonEx型にキャストするPersonEx.Equals(PersonEx)メソッドの呼び出しです。



PersonEx.Equals(Object)の実装が不要であることに注意してください。 それが存在せず、クライアントコードがEquals(Object)メソッドを呼び出した場合、継承されたメソッドPerson.Equals(Object)が呼び出され、内部で仮想メソッドPersonEx.Equals(Person)が呼び出され、PersonEx.Equals(PersonEx)の呼び出しにつながります。

ただし、PersonEx.Equals(Object)メソッドは、コードの「完全性」とパフォーマンスの向上のために実装されています(型変換と中間メソッド呼び出しの数を最小限に抑えることにより)。







つまり、PersonExクラスを作成してPersonクラスを継承することにより、Personクラスを作成してObjectクラスを継承する場合と同じことを行いました。



これで、PersonExクラスのオブジェクトのどのメソッドを呼び出しても、次のようになります。

等しい(PersonEx)、等しい(Person)、等しい(オブジェクト)、

オブジェクトの任意の1つのペアに対して、同じ結果が返されます(オペランドを交換すると、同じ結果が返されます)。

この動作を提供することにより、ポリモーフィズムが許可されます。







また、PersonExクラスに静的メソッドPersonEx.Equals(PersonEx、PersonEx)と、対応する比較演算子PersonEx。==(PersonEx、PersonEx)およびPersonEx。!=(PersonEx、PersonEx)も実装しました。 Personクラスを作成するとき。



PersonEx.Equals(PersonEx、PersonEx)メソッドまたはPersonEx。==(PersonEx、PersonEx)およびPersonEx。!=(PersonEx、PersonEx)演算子を使用して、オブジェクトの同じペアでEqualsインスタンスメソッドを使用した場合と同じ結果が得られます。クラスPersonEx。







しかし、それからもっと面白くなります。



PersonExクラスは、Personクラスから静的メソッドEquals(Person、Person)、および対応する比較演算子==(Person、Person)および!=(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;
      
      





Equals(Person、Person)メソッドと比較演算子==(Person、Person)および!=(Person、Person)は静的であるという事実にもかかわらず、結果は常にEquals(PersonEx、PersonExメソッドを呼び出すときと同じになります)、演算子==(PersonEx、PersonEx)および!=(PersonEx、PersonEx)、またはインスタンス仮想Equalsメソッドのいずれか。







このような多態的な動作を取得するために、静的なEqualsメソッドと比較演算子 "=="および "!="は、Equals仮想インスタンスメソッドを使用して継承の各段階で実装されます。



さらに、Equals(PersonEx、PersonEx)メソッドのPersonExクラス、および==(PersonEx、PersonEx)および!=(PersonEx、PersonEx)演算子の実装、およびPersonEx.Equals(Object)メソッドの実装はオプションです。

Equals(PersonEx、PersonEx)メソッドと==(PersonEx、PersonEx)および!=(PersonEx、PersonEx)演算子は、コードの完全性と高速化のために実装されています(型変換と中間メソッド呼び出しの数を最小限に抑える)。







静的なEqualsの「ポリモーフィズム」における唯一の不便な瞬間、「==」および「!=」は、PersonまたはPersonExタイプの2つのオブジェクトがオブジェクトタイプにキャストされる場合 、リンクを使用する==および!=演算子を使用してオブジェクトが比較されることです 、および値によってObject.Equals(Object、Object)メソッドを使用します。 しかし、これは「設計による」プラットフォームです。







続編では、オブジェクトの値による比較の実装の特徴- 構造体のインスタンスを検討し、オブジェクトのタイプの値によるオブジェクトの比較を実装することが本当に望ましい場合と、その方法を説明します。 客観的な観点から。




All Articles