GetHashCodeのいくつかの微妙な点

「フレームワーク設計ガイドライン:再利用可能な.NETライブラリの規則、イディオム、パターン」を読んでいると、次のフレーズに出会いました。



「オブジェクトに加えられた変更に関係なく、GetHashCodeがまったく同じ値を返すことを確認してください。」



うーん...私は彼らが何について話しているのだと思ったのですか? ReSharperによって生成される標準的な実装に気が付く前に、生成された値は、オブジェクトが変更されてもオブジェクトの寿命全体にわたって一定ではないことに気付きました。



問題の規模を理解するために例をスケッチすることを決めたので、人を反映するクラスがあり、一意の識別のために彼のSNILS番号を使用すると仮定します。



public class Employee { public string FirstName { get; set; } public string SecondName { get; set; } public string Snils { get; set; } protected bool Equals(Employee other) { return string.Equals(Snils, other.Snils); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != this.GetType()) return false; return Equals((Employee) obj); } public override int GetHashCode() { return (Snils != null ? Snils.GetHashCode() : 0); } }
      
      





オーバーロードされたメソッドは、ReSharperによって生成されます。 一見、すべてが正常です。 同等性チェックで使用されるフィールドは、ハッシュの生成に使用されます。 等しいオブジェクトは、等しいハッシュコードを持ちます。 すべてが素晴らしいようです。

いくつかのビジネスロジックを追加します。



 var employees = new HashSet<Employee>(); var employee = new Employee() { FirstName = "Sergei", SecondName = "Popov", Snils = "123456" }; employees.Add(employee); Console.WriteLine(employees.Contains(employee));
      
      





そして、「True」というメッセージが表示されます。

しかし、ある時点でSNILSを変更することにした場合



 var employees = new HashSet<Employee>() var employee = new Employee() { FirstName = "Sergei", SecondName = "Popov", Snils = "123456" }; employees.Add(employee); //      employee.Snils = "654321"; Console.WriteLine(employees.Contains(employee));
      
      





そして、「False」というメッセージが表示されます。



どうしたの?

内部的には、HashSetはいくつかのバスケットで構成されています。 オブジェクトのカートは、GetHashCodeによって返される値に基づいて選択されます。 SNILS番号を変更するとすぐに、GetHashCodeによって返される値も変更されました。 HashSetは、ハッシュコードに基づいて、表示用に別のバスケットを選択しました。もちろん、このバスケットにはオブジェクトが含まれていません(もちろん、ほとんどありません)。 他のバスケットでは、HashSetは表示されません。 等しいオブジェクトには、等しいGetHashCode値が必要です。 それだけです オブジェクトは見つかりません。



どのように機能しましたか?

Equals&GetHashCodeを再定義しなかった場合、オブジェクトのフィールドに加えた変更に関係なく、オブジェクトはオブジェクトの存続期間を通じて一定のGetHashCodeを持ちます。 ただし、これらのメソッドをオーバーロードする場合、ハッシュ生成アルゴリズムで不変のフィールドのみを使用する必要があり、生成アルゴリズムで使用されるフィールドを変更しないか、独自の松葉杖を考え出す必要があります(オプションとして、Objectクラスの標準実装で実装されたアプローチを使用できます) 。



したがって、道徳:

ハッシュコードの値は、オブジェクトの存続期間を通じて一定である必要があります。または、ケースで変更できる場合は、自分が何をしているかを明確に意識する必要があります。



PS。 Rocket Scienceから遠く離れた場所がここで説明されていることを理解しています。 ここに書かれていることはすべて明白であり、言及されている方法に対するマイクロソフトの要件に準拠しています。 ここにリッパートからの良い説明がありますが、すぐに私はそれに遭遇し、HashSetがFalseを返すとは信じませんでした。 あなたが今そこにいないことを願っています。



All Articles