12

参照型に対して特別なことを何もしなくても、Equals()参照の等価性 (つまり、同じオブジェクト) を意味します。参照型をオーバーライドすることを選択した場合Equals()、2 つのオブジェクトの値が等しいことを常に意味する必要がありますか?

この可変Personクラスを考えてみましょう:

class Person
{
    readonly int Id;

    string FirstName { get; set; }
    string LastName { get; set; }
    string Address { get; set; }
    // ...
}

まったく同じ人物を表す 2 つのオブジェクトの は常に同じIdですが、他のフィールドは時間の経過とともに (つまり、住所変更の前後で) 異なる場合があります。

このオブジェクトの場合、Equals を定義して、異なることを意味することができます。

  • 値が等しい: すべてのフィールドが等しい (同一人物を表すが住所が異なる 2 つのオブジェクトは false を返します)
  • ID の等価性:Idsは等しい (同一人物を表すがアドレスが異なる 2 つのオブジェクトは true を返します)
  • 参照の等価性: つまり、Equals を実装しないでください。

質問:このクラスに適しているのはどれですか? (または、「このクラスのほとんどのクライアントは、Equals() がどのように動作することを期待するでしょうか?」という質問が必要かもしれません)。

ノート:

  • Hashset値の等価性を使用すると、このクラスをまたはで使用することがより困難になります。Dictionary
  • Identity Equality を使用すると、Equals と演算子の間の関係が=奇妙になります (つまり、2 つの Person オブジェクト (p1 と p2) のチェックが に対して true を返した後でEquals()も、「新しい」 Person オブジェクトを指すように参照を更新する必要がある場合があります。同等の値ではありません)。たとえば、次のコードは奇妙に見えます。何もしていないように見えますが、実際には p1 を削除して p2 を追加しています。

    HashSet<Person> people = new HashSet<Person>();
    people.Add(p1);
    // ... p2 is an new object that has the same Id as p1 but different Address
    people.Remove(p2);
    people.Add(p2);
    

関連する質問:

4

3 に答える 3

13

はい、これに適切なルールを決定するのは難しいです。ここには単一の「正しい」答えはありません。コンテキストと好みの両方に大きく依存します。個人的には、ほとんどの通常の POCO クラスでデフォルトで等価を参照するだけで、あまり考えることはめったにありません。

  • ハッシュセットで辞書キー/のようなものを使用する場合の数Personは最小限です
    • その場合、従う必要のある実際のルールに従うカスタム比較子を提供できます
    • しかし、ほとんどの場合int Id、辞書 (など) のキーとして単純に使用します。
  • 参照等価を使用すると、 /がであっても であっても、または実際にジェネリック メソッドでx==y同じ結果が得られることを意味します。xyPersonobjectT
  • Equalsとが互換性がある限りGetHashCode、ほとんどのことはうまくいきます。それを行う簡単な方法の 1 つは、それらをオーバーライドしないことです。

ただし、値の型については常に反対のことをアドバイスすることに注意してください。つまり、/を明示的にオーバーライドします。しかし、その後、書くことは本当に珍しいですEqualsGetHashCodestruct

于 2013-07-10T14:50:58.173 に答える
6

複数のIEqualityComparer(T)実装を提供して、消費者に決定させることができます。

例:

// Leave the class Equals as reference equality
class Person
{
    readonly int Id;

    string FirstName { get; set; }
    string LastName { get; set; }
    string Address { get; set; }
    // ...
}

class PersonIdentityEqualityComparer : IEqualityComparer<Person>
{
    public bool Equals(Person p1, Person p2)
    {
        if(p1 == null || p2 == null) return false;

        return p1.Id == p2.Id;
    }

    public int GetHashCode(Person p)
    {
        return p.Id.GetHashCode();
    }
}

class PersonValueEqualityComparer : IEqualityComparer<Person>
{
    public bool Equals(Person p1, Person p2)
    {
        if(p1 == null || p2 == null) return false;

        return p1.Id == p2.Id &&
               p1.FirstName == p2.FirstName; // etc
    }

    public int GetHashCode(Person p)
    {
        int hash = 17;

        hash = hash * 23 + p.Id.GetHashCode();
        hash = hash * 23 + p.FirstName.GetHashCode();
        // etc

        return hash;
    }
}

参照:オーバーライドされた System.Object.GetHashCode に最適なアルゴリズムは何ですか?

使用法:

var personIdentityComparer = new PersonIdentityEqualityComparer();
var personValueComparer = new PersonValueEqualityComparer();

var joseph = new Person { Id = 1, FirstName = "Joseph" }

var persons = new List<Person>
{
   new Person { Id = 1, FirstName = "Joe" },
   new Person { Id = 2, FirstName = "Mary" },
   joseph
};

var personsIdentity = new HashSet<Person>(persons, personIdentityComparer);
var personsValue = new HashSet<Person>(persons, personValueComparer);

var containsJoseph = personsIdentity.Contains(joseph);
Console.WriteLine(containsJoseph); // false;

containsJoseph = personsValue.Contains(joseph);
Console.WriteLine(containsJoseph); // true;
于 2013-07-10T14:53:46.240 に答える
1

基本的に、クラス型フィールド (または変数、配列スロットなど)Xとそれぞれがクラス オブジェクトへの参照を保持している場合、答えYられる論理的な質問が 2 つあります。(Object)X.Equals(Y)

  1. `Y` の参照が `X` にコピーされた (参照がコピーされたことを意味する) 場合、クラスには、そのような変更が何らかの方法でプログラムのセマンティクスに影響を与えることを期待する理由があるでしょうか (たとえば、現在の *または将来の動作に影響を与えることによって)。 `X` または `Y` の任意のメンバーの)
  2. `X` のターゲットへの *すべて* の参照が `Y` のターゲットを指すように瞬時に魔法のように作成された場合、*およびその逆*`、クラスはプログラムの動作を変更するためのそのような変更を期待する必要があります (たとえば、 *ID ベースの `GetHashCode`* 以外の任意のメンバー、またはストレージの場所に互換性のない型のオブジェクトを参照させることによる)。

Xと が異なる型のオブジェクトを参照する場合、どちらの関数も正当に true を返さないことに注意してください。ただしY、両方のクラスが、一方への参照を保持し、他方への参照も保持できない記憶域の場所が存在できないことを認識している場合を除きます [たとえば、両方の型が非公開であるなどの理由で]共通のベースから派生したクラスであり、型が両方への参照を保持できないストレージの場所 ( 以外this) にはどちらも格納されません]。

デフォルトのObject.Equals方法は最初の質問に答えます。ValueType.Equals2番目に答えます。最初の質問は、通常、監視可能な状態が変更される可能性のあるオブジェクト インスタンスについて尋ねる適切な質問です。2 番目は、オブジェクトのインスタンスのタイプが許可されていても、監視可能な状態が変更されないことを確認するのに適しています。XYそれぞれが個別の への参照を保持し、両方の配列が最初の要素に 23 を保持している場合int[1]、最初の等価関係はそれらを別個のものとして定義する必要があります [ にコピーXするとifが変更された場合Yの動作が変更されます] が、2 番目はそれらを同等と見なす必要があります。 (およびのターゲットへのすべての参照を交換します。X[0]Y[0]XY何も影響しません)。X[0]配列が異なる値を保持している場合、オブジェクトを交換すると、以前は報告されていた値が報告されることになるため、2 番目のテストでは配列を別個のものと見なす必要があることに注意してくださいY[0])。

可変型 (System.ValueTypeおよびその子孫を除く) はObject.Equals、最初の型の等価関係を実装するためにオーバーライドする必要があるというかなり強力な規則があります。System.ValueTypeまたはその子孫が最初のリレーションを実装することは不可能であるため、通常は 2 番目のリレーションを実装します。Object.Equals()残念ながら、任意の型の任意の 2 つのオブジェクト間の比較を許可する等価関係を定義できたとしても、最初の種類の関係をオーバーライドするオブジェクトが 2 番目の種類をテストするメソッドを公開するという標準的な規則はありません。2 番目の関係は、不変クラスImmが可変型へのプライベート参照を保持する標準パターンで役立ちます。Mutただし、そのオブジェクトを実際に変更する可能性のあるコードには公開しません [インスタンスを不変にする]。Mutそのような場合、インスタンスが決して書き込まれないことをクラスが知る方法はありませんが、 の 2 つのインスタンスが参照を保持している s に対して、次の場合にそれらが同等であるかどうかをImm尋ねることができる標準的な手段があると便利です。参照の所有者はそれらを決して変更しませんでした。上記で定義された等価関係は、突然変異や、インスタンスが突然変異しないことを保証するために使用しなければならない特定の手段について言及していないことに注意してください。ただし、その意味は、いずれの場合も明確に定義されています。への参照を保持するオブジェクトMutImmMutその参照がアイデンティティ、可変状態、または不変状態をカプセル化するかどうかを知っている必要があり、したがって、独自の等価関係を適切に実装できる必要があります。

于 2013-07-10T15:32:30.820 に答える