以前の出版物では、Personクラスを例として使用して、 .NETプラットフォームの値でオブジェクトを比較するための実装オプションを受け取りました。
オブジェクトの任意の同じペアの各比較メソッドは、同じ結果を返します。
コード例Person p1 = new Person("John", "Smith", new DateTime(1990, 1, 1)); Person p2 = new Person("John", "Smith", new DateTime(1990, 1, 1));
さらに、各比較方法は可換です:
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) {
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) {
MiddleNameのもう1つの重要なプロパティが後継クラスに登場しました。 したがって、最初に必要なこと:
- IEquatable(Of PersonEx)インターフェイスを実装します。
- PersonEx.Equals(Person)メソッドを実装し、継承されたPerson.Equals(Person)メソッドをオーバーライドし(継承の可能性を考慮するために後者が最初に仮想宣言されたことに注意する価値があります)、Person型のオブジェクトをPersonEx型にキャストしようとします。
(それ以外の場合、MiddleNameを除くすべてのキーフィールドが等しいオブジェクトを比較すると、「オブジェクトが等しい」という結果が返されますが、これはサブジェクトの観点からは正しくありません。)
この場合:
- PersonEx.Equals(PersonEx)メソッドの実装は、Person.Equals(Person)メソッドの実装に似ています。
- PersonEx.Equals(Person)メソッドの実装は、Person.Equals(Object)メソッドの実装に似ています。
- 静的保護メソッドEqualsHelper(PersonEx、PersonEx)の実装は、EqualsHelper(Person、Person)メソッドの実装に似ています。 コードを再利用するには、後者が最初の方法で使用されます。
次に、継承された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));
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)メソッドを使用します。 しかし、これは「設計による」プラットフォームです。
続編では、オブジェクトの値による比較の実装の特徴- 構造体のインスタンスを検討し、オブジェクトのタイプの値によるオブジェクトの比較を実装することが本当に望ましい場合と、その方法を説明します。 客観的な観点から。