49

私はドメインモデルを使用していて、これら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();
}
4

7 に答える 7

38

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

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

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

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

于 2010-03-02T12:55:25.227 に答える
12

ここでの答えはどれも、私にとって実際に当てはまるものではありませんでした。平等には使用できないと既に述べており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;
}

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

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

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

于 2016-01-21T00:14:16.157 に答える
6

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

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

public override int GetHashCode() {
    return object1.GetHashCode ^ intValue1 ^ (intValue2 << 16);
}
于 2010-03-02T12:50:59.587 に答える
4

私はこの古い質問に出くわしましたが、私見では、@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;
}

それで全部です!

于 2018-03-27T17:02:12.980 に答える
3

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

于 2010-03-02T12:51:03.557 に答える
1

回答に加えて(コメントを書くことは許可されていません)、VisualStudioがEqualsとGetHashCodeを自動生成できることを指摘したいと思います。この回答を参照してください:Visual Studioでequalsおよびhashcodeメソッドを自動的に生成する方法はあります か?私は本当にそのカスタム実装を探していましたが、ここでは見つかりませんでした。

また、この質問をリンクしたいと思い ます。2つの複雑なオブジェクトを比較する最良の方法 それは、ネストされたクラス構造を持つことです。コメントの下に、列挙型(たとえば、List)を使用したネストされたクラス構造のケースを見つけることができます。

于 2020-11-23T13:29:56.403 に答える
0

上記の回答と私自身の経験に基づいて、いくつかの具体的なシナリオを見てみたいと思います。

経験則では、ハッシュコードが異なる2つのインスタンスは常に等しくないはずですが、ハッシュコードが同じである場合、等しい場合と等しくない場合があります。GetHashCode()インスタンスをすばやく区別Equals()するために使用され、同等性を検証するために使用されます(それがあなたにとって意味するものは何でも)。

また、多くの組み込みメカニズムはの実装を探すので、実際にチェックをIEquatable<T>行うオーバーライドを宣言することをお勧めします。Equals(MyClass)

一意のIDを持つクラス

一意のIDを持つクラスについて考えてみます。次に、equals操作はIDをチェックするだけです。IDのみに依存するハッシュについても同じです。

public class IdClass : IEquatable<IdClass>
{
    public int ID { get; } // Assume unique
    public string Name { get; }


    #region IEquatable Members
    /// <summary>
    /// Equality overrides from <see cref="System.Object"/>
    /// </summary>
    /// <param name="obj">The object to compare this with</param>
    /// <returns>False if object is a different type, otherwise it calls <code>Equals(IdClass)</code></returns>
    public override bool Equals(object obj)
    {
        if (obj is IdClass other)
        {
            return Equals(other);
        }
        return false;
    }

    /// <summary>
    /// Checks for equality among <see cref="IdClass"/> classes
    /// </summary>
    /// <param name="other">The other <see cref="IdClass"/> to compare it to</param>
    /// <returns>True if equal</returns>
    public virtual bool Equals(IdClass other)
    {
        if (other == null) return false;
        return ID.Equals(other.ID);
    }

    /// <summary>
    /// Calculates the hash code for the <see cref="IdClass"/>
    /// </summary>
    /// <returns>The int hash value</returns>
    public override int GetHashCode() => ID.GetHashCode();

    #endregion

}

プロパティを持つクラス

この場合は上記と似ていますが、比較は2つ以上のプロパティに依存するため、ハッシュコードで非対称に組み合わせる必要があります。これは次のシナリオでより明らかになりますが、一方のプロパティにハッシュがAあり、もう一方のプロパティにハッシュがある場合、最初のプロパティにハッシュがあり、もう一方のBプロパティがハッシュである場合とは結果が異なるはずです。BA

public class RefClass : IEquatable<RefClass>
{
    public string Name { get; }
    public int Age { get; }


    #region IEquatable Members
    /// <summary>
    /// Equality overrides from <see cref="System.Object"/>
    /// </summary>
    /// <param name="obj">The object to compare this with</param>
    /// <returns>False if object is a different type, otherwise it calls <code>Equals(RefClass)</code></returns>
    public override bool Equals(object obj)
    {
        if (obj is RefClass other)
        {
            return Equals(other);
        }
        return false;
    }

    /// <summary>
    /// Checks for equality among <see cref="RefClass"/> classes
    /// </summary>
    /// <param name="other">The other <see cref="RefClass"/> to compare it to</param>
    /// <returns>True if equal</returns>
    public virtual bool Equals(RefClass other)
    {
        if (other == null) { return false; }
        return Name.Equals(other.Name)
            && Age.Equals(other.Age);
    }

    /// <summary>
    /// Calculates the hash code for the <see cref="RefClass"/>
    /// </summary>
    /// <returns>The int hash value</returns>
    public override int GetHashCode()
    {
        unchecked
        {
            int hc = -1817952719;
            hc = (-1521134295) * hc + Name.GetHashCode();
            hc = (-1521134295) * hc + Age.GetHashCode();
            return hc;
        }
    }

    #endregion

}

値ベースのクラス(構造)

structこれは、値型(宣言)であるためにequalsを再定義し、呼び出す必要==!=あることを除いて、上記の場合とほとんど同じです。

public struct ValClass : IEquatable<ValClass>
{
    public int X { get; }
    public int Y { get; }

    #region IEquatable Members
    /// <summary>
    /// Equality overrides from <see cref="System.Object"/>
    /// </summary>
    /// <param name="obj">The object to compare this with</param>
    /// <returns>False if object is a different type, otherwise it calls <code>Equals(ValClass)</code></returns>
    public override bool Equals(object obj)
    {
        if (obj is ValClass other)
        {
            return Equals(other);
        }
        return false;
    }

    public static bool operator ==(ValClass target, ValClass other) { return target.Equals(other); }
    public static bool operator !=(ValClass target, ValClass other) { return !(target == other); }


    /// <summary>
    /// Checks for equality among <see cref="ValClass"/> classes
    /// </summary>
    /// <param name="other">The other <see cref="ValClass"/> to compare it to</param>
    /// <returns>True if equal</returns>
    public bool Equals(ValClass other)
    {
        return X == other.X && Y == other.Y;
    }

    /// <summary>
    /// Calculates the hash code for the <see cref="ValClass"/>
    /// </summary>
    /// <returns>The int hash value</returns>
    public override int GetHashCode()
    {
        unchecked
        {
            int hc = -1817952719;
            hc = (-1521134295) * hc + X.GetHashCode();
            hc = (-1521134295) * hc + Y.GetHashCode();
            return hc;
        }
    }

    #endregion

}

struct不変である必要があることに注意してくださいreadonly。宣言にキーワードを追加することをお勧めします

public readonly struct ValClass : IEquatable<ValClass>
{
} 
于 2020-11-23T14:20:32.450 に答える