web-dev-qa-db-ja.com

EqualsとGetHashCodeの最良の戦略は何ですか?

私はドメインモデルを使用していて、これら2つのメソッドを.NETに実装するために必要なさまざまな方法について考えていました。あなたの好ましい戦略は何ですか?

これは私の現在の実装です:

public override bool Equals(object obj)
{
    var newObj = obj as MyClass;

    if (null != newObj)
    {
        return this.GetHashCode() == newObj.GetHashCode();
    }
    else
    {
        return base.Equals(obj);
    }
}

// Since this is an entity I can use its Id
// When I don't have an Id, I usually make a composite key of the properties
public override int GetHashCode()
{
    return String.Format("MyClass{0}", this.Id.ToString()).GetHashCode();
}
41
tucaz

ハッシュコードが等しいためにインスタンスが等しいと仮定するのは誤りです。

GetHashCodeの実装は問題ないと思いますが、私は通常、次のようなものを使用します。

public override int GetHashCode() {
    return object1.GetHashCode ^ intValue1 ^ (intValue2 << 16);
}
3
erikkallen

ドメイン駆動設計エンティティ値オブジェクト。これは、Equalsの実装方法をガイドするため、注意すべき良い区別です。

エンティティは、IDが互いに等しい場合に等しくなります。

値オブジェクトは、それらの(重要な)構成要素がすべて互いに等しい場合に等しくなります。

いずれにせよ、GetHashCodeの実装は、同等性を判断するために使用されるのと同じ値に基づいている必要があります。つまり、エンティティの場合、ハッシュコードはIDから直接計算する必要がありますが、値オブジェクトの場合は、すべての構成値から計算する必要があります。

35
Mark Seemann

ここでの答えはどれも、私にとって実際にその場に出ることはありませんでした。平等のためにIdを使用することはできず、プロパティのバンドルを使用する必要があると既に述べたので、これを行うためのより良い方法があります。注:これがEqualsGetHashCodeを実装するための最良の方法であるとは全体的に考えていません。これは、OPのコードのより良いバージョンです。

public override bool Equals(object obj) {
   var myClass = obj as MyClass;

   if (myClass != null) {
      // Order these by the most different first.
      // That is, whatever value is most selective, and the fewest
      // instances have the same value, put that first.
      return this.Id == myClass.Id
         && this.Name == myClass.Name
         && this.Quantity == myClass.Quantity
         && this.Color == myClass.Color;
   } else {
      // This may not make sense unless GetHashCode refers to `base` as well!
      return base.Equals(obj);
   }
}

public override int GetHashCode() {
   int hash = 19;
   unchecked { // allow "wrap around" in the int
      hash = hash * 31 + this.Id; // assuming integer
      hash = hash * 31 + this.Name.GetHashCode();
      hash = hash * 31 + this.Quantity; // again assuming integer
      hash = hash * 31 + this.Color.GetHashCode();
   }
   return hash;
}

この背後にある理由のいくつかについては、 Jon Skeetによるこの回答 を参照してください。さまざまなデータセットが同じハッシュになる可能性があるため、xorの使用は適切ではありません。素数(上記の19と31のシード値、または選択した他の値)を使用したこのラップアラウンド方法は、それぞれの衝突がほとんどない「バケット」にセグメント化するのに適しています。

値のいずれかがnullになる可能性がある場合は、それらをどのように比較するかを慎重に検討することをお勧めします。おそらく、短絡ヌル評価とヌル合体演算子を使用できます。ただし、nullを同等と比較する必要がある場合は、nullの場合に異なるハッシュコードを異なるnull許容プロパティに割り当てるようにしてください。

また、あなたのEqualsの実装が意味をなさないと私は確信していません。 2つのオブジェクトが等しいかどうかを比較する場合、最初にそれらのGetHashCode値が比較されます。それらが異なる場合にのみ、Equalsメソッドが実行されます(同じ値にハッシュする2つのオブジェクトが異なる場合、これが検出されます)。 GetHashCode実装はbaseを参照していないため、Equalsメソッドが参照することは意味がない場合があります。具体的には、ハッシュコードが異なる2つのオブジェクトに対してEqualsがtrueを返すことができる場合、問題が発生するのを待っている重大なバグが発生します。

7
ErikE

私はこの古い質問に出くわしましたが、私見では、@ tucazによって作成された元の質問を明確かつ単純に述べた答えは見つかりませんでした。

上(または下:D)で共有されている多くの考慮事項に同意できますが、「疑問符」が欠落しています(私は思います)。

ただし:

  • エンティティには平等が必要です
  • エンティティ-オブジェクトが同じエンティティをマップする場合、オブジェクトは等しいと見なすことができます。つまり、同じ"エンティティキー"を参照します
  • @tucazで示されている例では、"Id"について言及しています(過剰に実装されたGetHashCode()を参照)…バギーは言うまでもありませんEquals(…)

簡単な実装の1つは次のようになると推測できます。

public class MyEntity: IEquatable<MyEntity> {
    int Id;

    public MyEntity(int id){
        Id = id;
    }

    public override bool Equals(object obj) => Equals(obj as MyEntity);
    public bool Equals(MyEntity obj) => obj != null && Id == obj.Id;
    public override int GetHashCode() => Id;
}

それで全部です!

4
Lord of the Goo

ハッシュコードは衝突する可能性があるため、同等性を比較するのに適した方法ではないと思います。代わりに、オブジェクトを「等しく」する基本的な値を比較する必要があります。この質問に対する@JonSkeetの回答を参照してください: オーバーライドされたSystem.Object.GetHashCodeに最適なアルゴリズムは何ですか? 同等性に複数のプロパティが含まれる場合のGetHashCodeの実装を改善するには。単一のプロパティの場合は、ハッシュコードを再利用できます。

3
tvanfosson