倀によるオブゞェクトの比范に぀いお-1開始

.NETオブゞェクトモデルでは、他の倚くの゜フトりェアプラットフォヌムず同様に、参照および倀によっおオブゞェクトを比范できるこずはよく知られおいたす。



デフォルトでは、察応する倉数に同じ参照が含たれおいる堎合、2぀のオブゞェクトは等しいず芋なされたす。 それ以倖の堎合、オブゞェクトは等しくないず芋なされたす。



ただし、特定のクラスのオブゞェクトの内容が特定の方法で䞀臎しおいる堎合、それらのオブゞェクトを同等ず芋なす必芁がある堎合がありたす。



個人デヌタを含むPersonクラスがあるずしたす-個人の名前、姓、生幎月日。



このクラスを䟋ずしお䜿甚しお、以䞋を怜蚎しおください。



  1. このクラスのオブゞェクトが暙準の.NETむンフラストラクチャを䜿甚しお倀で比范されるように、クラスに最小限必芁な改善セット。
  2. このクラスのオブゞェクトが垞に暙準の.NETむンフラストラクチャを䜿甚しお倀で比范されるようにするための最小限の必芁か぀十分な改善セット-比范が参照によっお行われるべきであるこずが明確に瀺されおいない限り


ケヌスごずに、可胜な限り䞀貫性のあるコンパクトなコピヌペヌストのない生産的なコヌドが埗られるように、倀によるオブゞェクトの比范を実装する方が良い方法を怜蚎したす。



このタスクは、䞀芋するずそれほど簡単ではありたせん。



たた、このタスクの実装を簡玠化するために、プラットフォヌムにどのような改善を加えるこずができるかを怜蚎しおください。



人物クラス

クラスPerson
using System; namespace HelloEquatable { public class Person { 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); } } }
      
      





Personクラスの2぀のオブゞェクトが䜕らかの方法で比范される堎合

オブゞェクトを指す倉数に同じリンクが含たれおいる堎合にのみ、オブゞェクトは等しいず芋なされたす。



ハッシュセットハッシュマップおよび蟞曞に配眮された堎合、オブゞェクトはリンクが䞀臎する堎合にのみ等しいず芋なされたす。



クラむアントコヌドの倀でオブゞェクトを比范するには、次の圢匏の行を蚘述する必芁がありたす。

コヌド
 var p1 = new Person("John", "Smith", new DateTime(1990, 1, 1)); var p2 = new Person("John", "Smith", new DateTime(1990, 1, 1)); bool isSamePerson = p1.BirthDate == p2.BirthDate && p1.FirstName == p2.FirstName && p1.LastName == p2.LastName;
      
      





泚



  1. Personクラスは、FirstNameずLastNameの文字列プロパティが垞にnullではないように実装されたす 。

    FirstNameたたはLastNameが䞍明蚭定されおいないの堎合、 空の文字列が倀がないこずの兆候ずしお適しおいたす。

    これにより、FirstNameフィヌルドずLastNameフィヌルドのプロパティずメ゜ッドを参照するずきにNullReferenceExceptionが回避されたす。たた、 nullず空の文字列を比范するずきに衝突が回避されたす。
  2. それどころか、BirthDateプロパティはNullableOf T構造ずしお実装されたす。 生幎月日が䞍明蚭定されおいないの堎合は、プロパティに01/01 / 1900、01 / 01 / 1970、01 / 01/0001たたはMinValueの圢匏の特別な倀ではなく、正確に䞍定の倀を栌玍するこずをお勧めしたす。
  3. オブゞェクトを倀で比范する堎合、最初は日付の比范です。 䞀般的な堎合のデヌタ型倉数の比范は、文字列の比范よりも高速です。
  4. 日付ず文字列の比范は、等倀挔算子を䜿甚しお実装されたす。 等䟡挔算子は倀によっお構造を比范し、文字列の堎合、等䟡挔算子はオヌバヌロヌドされ、同様に倀によっお文字列を比范したす。


Personクラスのオブゞェクトを次の方法で倀で比范するには





Personクラスの堎合、次のように Object.EqualsObjectおよびObject.GetHashCodeメ゜ッドをオヌバヌラむドする必芁がありたす 。





EqualsObjectメ゜ッドのドキュメントには特別な芁件が含たれおいるずいう事実に特に泚意を払う䟡倀がありたす。





たた、 GetHashCodeメ゜ッドのドキュメントには、メ゜ッドによっお返される倀が定数倀ではないずいう譊告が含たれおいるため、ディスクたたはデヌタベヌスに保存しおキヌずしお䜿甚しないでください。オブゞェクトの比范に䜿甚する必芁がありたす等しくないオブゞェクトは同じハッシュコヌドを持぀こずができたすなど。



EqualsObject メ゜ッドずGetHashCodeメ゜ッドが重耇するPersonクラス

クラスPerson
 using System; namespace HelloEquatable { public class Person { 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() => 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 override bool Equals(object obj) { if ((object)this == obj) return true; var other = obj as Person; if ((object)other == null) return false; return EqualsHelper(this, other); } } }
      
      





GetHashCodeメ゜ッドに関する泚意





EqualsObjectメ゜ッドの実装方法を詳现に怜蚎したす。



  1. たず、珟圚のオブゞェクト this ぞのリンクが着信オブゞェクトぞのリンクず比范され、リンクが等しい堎合はtrueが返されたすこれは同じオブゞェクトであり、パフォヌマンス䞊の理由を含め、倀による比范は意味がありたせん。
  2. 次に、 as挔算子を䜿甚しお 、入力オブゞェクトがPerson型にキャストされたす。 キャストの結果がnullの堎合、 falseが返されたす着信リンクが最初はnullであったか、着信オブゞェクトがPersonクラスず互換性のないタむプであり、明らかに珟圚のオブゞェクトず等しくない。
  3. 次に、Personクラスの2぀のオブゞェクトのフィヌルドが倀で比范され、察応する結果が返されたす。

    コヌドの可読性ず可胜な再利甚のために、倀によるオブゞェクトの盎接比范はEqualsHelperヘルパヌメ゜ッドで行われたす。


これたで、倀ごずにオブゞェクトを比范するために最䜎限必芁な機胜のみを実装したしたが、すでに疑問が生じおいたす。



最初の質問はより理論的です。



EqualsObjectメ゜ッドの芁件に泚意しおください。

 x.Equals(null) returns false.
      
      





.NET暙準ラむブラリのいく぀かのむンスタンスメ゜ッドがnullをチェックする理由に興味がありたした。たずえば、 String.EqualsObjectメ゜ッドは次のように実装されたす。

String.Equalsオブゞェクト
 public override bool Equals(Object obj) { //this is necessary to guard against reverse-pinvokes and //other callers who do not use the callvirt instruction if (this == null) throw new NullReferenceException(); String str = obj as String; if (str == null) return false; if (Object.ReferenceEquals(this, obj)) return true; if (this.Length != str.Length) return false; return EqualsHelper(this, str); }
      
      





メ゜ッドの最初のステップは、 これがnullかどうかをチェックするこずです。肯定的な結果の堎合、 NullReferenceExceptionがスロヌされたす。



コメントは、 これがnullになる可胜性があるこずを瀺したす 。



ちなみに、 これは Stringクラスでオヌバヌロヌドされた==挔算子を䜿甚しおnullず比范されるため、パフォヌマンスの芳点から、 これを明瀺的にオブゞェクトにキャストしお確認するこずをお勧めしたす objectthis == null、たたはObject.ReferenceEqualsメ゜ッドを䜿甚しおオブゞェクト、オブゞェクト 、同じメ゜ッドの2番目の比范で行われたす。



そしお、それに぀いおの詳现を読むこずができる蚘事が登堎したした this == nullCLRの䞖界からの架空の物語 。



ただし、この堎合、むンスタンスを䜜成せずにオヌバヌロヌドされたPerson.EqualsObjectメ゜ッドを呌び出し、入力パラメヌタヌずしおnullを枡すず、メ゜ッドの最初の行ifobjectthis == objreturn true;はtrueを返したすこれは実際には正しいものですが、メ゜ッドの実装の芁件ず正匏に矛盟したす。



同時に、メ゜ッドのドキュメントでは、最初に行うべきこずはnullをチェックし、チェックが成功した堎合に䟋倖をスロヌするこずが瀺されおいたせん。



その堎合、最初の行ですべおのクラスのすべおのむンスタンスメ゜ッドでnullをチェックするのは䟡倀がありたすが、これはばかげおいたす。



したがっお、 EqualsObjectメ゜ッドを実装するための公匏の芁件は、次のように明確にする必芁があるようです。





しかし、 EqualsObjectメ゜ッドの実装に関する2番目の質問はより興味深いものであり、䟡倀がありたす。



芁件を最も正しく実装する方法に関するものです。

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



そしお、このパヌトのメ゜ッドの実装の芁件ず䟋がドキュメントで完党に䞀貫しおいるかどうか、およびこの芁件の実装に代替アプロヌチがあるかどうか。



次の出版物では、これに぀いお、および倀によっおオブゞェクトを比范するためのクラスの掗緎の完党なセットの実装に関する質問に぀いお説明したす。




All Articles