web-dev-qa-db-ja.com

演算子==を定義しているが、Equals()またはGetHashCode()を定義していないことの何が問題になっていますか?

以下のコードの場合

public struct Person
{
    public int ID;
    public static bool operator ==(Person a, Person b) { return  a.Equals(b); }
    public static bool operator !=(Person a, Person b) { return !a.Equals(b); }
}

コンパイラがこれらの警告を表示するのはなぜですか?
以下のメソッドを定義しないことの何が問題になっていますか?

warning CS0660: 'Person' defines operator == or operator != but
    does not override Object.Equals(object o)

warning CS0661: 'Person' defines operator == or operator != but
    does not override Object.GetHashCode()
23
user541686

[〜#〜] edit [〜#〜]:この回答は修正されました。特に、ユーザー定義の値型は修正されていないことに注意してください。 ==を生成し、 ValueType.Equals のパフォーマンスの問題について言及します。


一般に、すべてではありませんが、1つを上書きすると混乱します。ユーザーは、同じセマンティクスでオーバーライドされないこと、または両方がオーバーライドされることを期待します。

Microsoftの 推奨事項 この状態(とりわけ):

  • Equalsメソッドを実装するときはいつでも、GetHashCodeメソッドを実装してください。これにより、EqualsとGetHashCodeの同期が維持されます。

  • 等式演算子(==)を実装するときはいつでも、Equalsメソッドをオーバーライドし、同じことを実行させます。

あなたの場合、Equals(コンパイラは自動的に==を実装しません)を延期し、それら2つ(==/!=)のみをオーバーライドする正当な理由があります。 。ただし、ValueType.Equalsはリフレクションを使用するため、パフォーマンスの問題がまだあります。

「特定のタイプのEqualsメソッドをオーバーライドして、メソッドのパフォーマンスを向上させ、タイプの同等性の概念をより厳密に表現します。」

したがって、最後にすべて(==/!=/Equals)をオーバーライドすることをお勧めします。もちろん、この些細な構造体ではパフォーマンスは重要ではないかもしれません。

20

フレームワーク内には、 特定の操作 が常に同じ結果を生成する必要があるという一般的な期待があります。その理由は、特定の操作(特に、アプリケーションの大部分を占める並べ替えと検索)がこれらのさまざまな操作に依存して、意味のある一貫した結果を生成するためです。この場合、あなたはそれらの仮定のいくつかを破っています:

  • abの間に有効な操作_==_がある場合、a.Equals(b)と同じ結果が生成されます。
  • 同様に、abの間に有効な操作_!=_がある場合、!a.Equals(b)と同じ結果が生成されます。
  • 2つのオブジェクトabが存在し、その_a == b_の場合、abは、ハッシュに格納されたときに同じキーを生成する必要がありますテーブル。

最初の2つであるIMOは明らかです。 2つのオブジェクトが等しいことの意味を定義する場合は、2つのオブジェクトが等しいことを確認できるすべての方法を含める必要があります。コンパイラは、実際にこれらのルールに従うことを強制しないことに注意してください(通常、cannot)。オペレーターの本体の複雑なコード分析を実行して、オペレーターがすでにEqualsを模倣しているかどうかを確認することはありません。最悪の場合、停止問題の解決 と同等である可能性があるためです。

ただし、実行できることは、これらのルールに違反している可能性が最も高いケースをチェックすることです。特に、カスタム比較演算子を提供し、カスタムEqualsメソッドを提供していません。ここでの前提は、演算子に特別なことをさせたくない場合は、わざわざ演算子を提供する必要がないということです。その場合、必要なメソッドのallにカスタム動作を提供する必要があります。同期中。

Equalsを_==_とは異なるものに実装した場合、コンパイラは文句を言いません。あなたは、C#があなたが愚かなことをするのを防ごうとするのがどれほど難しいかという限界に達したでしょう。誤ってコードに微妙なバグを導入するのを防ぐことはできましたが、それが必要な場合は、意図的にそうすることができます。

3番目の仮定は、フレームワークの多くの内部操作がハッシュテーブルのバリアントを使用するという事実と関係があります。私の定義では「等しい」という2つのオブジェクトがある場合、これを実行できるはずです。

_if (a == b)
{
    var tbl = new HashTable();
    tbl.Add(a, "Test");

    var s = tbl[b];
    Debug.Assert(s.Equals("Test"));
}
_

これはハッシュテーブルの基本的なプロパティであり、突然真でないと非常に奇妙な問題を引き起こします。

5

コンパイラは、==メソッドでEqualsを使用していることを認識していないため、これらの警告が表示されていると思います。

この実装があるとします

public struct  Person
{
    public int ID;
    public static bool operator ==(Person a, Person b) { return Math.Abs(a.ID - b.ID) <= 5; }
    public static bool operator !=(Person a, Person b) { return Math.Abs(a.ID - b.ID) > 5; }
}

次に

 Person p1 = new Person() { ID = 1 };
 Person p2 = new Person() { ID = 4 };

 bool b1 = p1 == p2;
 bool b2 = p1.Equals(p2);

b1はtrueになりますが、b2false

-編集-

今、あなたがこれをしたいとします

Dictionary<Person, Person> dict = new Dictionary<Person, Person>();
dict.Add(p1, p1);
var x1 = dict[p2]; //Since p2 is supposed to be equal to p1 (according to `==`), this should return p1

しかし、これはKeyNotFoundのような例外をスローします

しかし、あなたが追加した場合

public override bool Equals(object obj)
{
    return Math.Abs(ID - ((Person)obj).ID) <= 5; 
}
public override int GetHashCode()
{
    return 0;
}

あなたはあなたが望むものを手に入れるでしょう。

コンパイラは、同様の条件に直面する可能性があることを警告するだけです

3
L.B

あなたがする必要があるのはあなたの構造体にForenameと言う別のメンバーを追加することです。

では、IDが63でフォレナームが異なる2人の人がいる場合、それらは等しいかどうか?

すべては、実装する「同じ」の定義によって異なります。

より良い例の構造体を使用し、さまざまなメソッドを実行するためのうなずきアプリケーションを記述し、同等性または同等性の定義を変更するとどうなるかを確認します。すべてが一致していない場合は、!(a == b)!=(a!= b)、これは本当かもしれませんが、コードを使用するすべてのメソッドをオーバーライドしないと、あなたの意図が何であったか疑問に思うでしょう。

基本的に、コンパイラはあなたに善良な市民であり、あなたの意図を明確にするように言っています。

1
Tony Hopkinson

EqualsGetHashCodeをオーバーライドする場合、演算子をオーバーライドする必要すらありません。これは、よりクリーンなアプローチです。編集:これは構造体であるため、機能するはずです。

0
AD.Net

MSDNページをお読みください。

CS066

CS0661

コンパイラは基本的に次のように言っています。「オブジェクトを比較する方法を知っていると言っているので、常にそのように比較する必要があります。」

0
Kendall Frey
public struct Coord
{
    public int x;
    public int y;

    public Coord(int x, int y)
    {
        this.x = x;
        this.y = y;
    }

    public static bool operator ==(Coord c1, Coord c2)
    {
        return c1.x == c2.x && c1.y == c2.y;
    }

    public static bool operator !=(Coord c1, Coord c2)
    {
        return !(c1 == c2);
    }

    public bool Equals(Coord other)
    {
        return x == other.x && y == other.y;
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        return obj is Coord && Equals((Coord) obj);
    }

    public override int GetHashCode()
    {
        return 0;
    }
}

これが例です。うまくいけば、それは役に立ちます。

0
Arwego

おそらく、デフォルトのEquals()メソッドが実際のシステムに十分であるとは期待されていないためです(たとえば、クラスではIDフィールドを比較する必要があります)。

0
Rob I