48

最近これに遭遇しました。これまで、2 つの参照型に実際に同じデータが含まれているかどうか(つまり、同じように見える 2 つの異なるインスタンス)を確認するために、等価演算子 ( == ) および/またはEqualsメソッドを喜んでオーバーライドしてきました。

自動テスト (参照/期待データと返されたデータの比較) に慣れてきたので、これをさらに使用しています。

MSDN のコーディング標準ガイドラインのいくつかを調べているときに、それを勧める記事に出くわしました。これで、記事がこれを言っている理由を理解しました(それらは同じインスタンスではないため)が、質問には答えていません:

  1. 2 つの参照型を比較す​​る最良の方法は何ですか?
  2. IComparableを実装する必要がありますか? (これは値型のみに予約する必要があるという言及も見ました)。
  3. 私が知らないインターフェースはありますか?
  4. 私たちは自分自身を転がすべきですか?

どうもありがとう^_^

アップデート

私はいくつかのドキュメントを読み間違えたようです (長い一日でした)

参照型を実装している場合、型が Point、String、BigNumber などの基本型のように見える場合は、参照型で Equals メソッドをオーバーライドすることを検討する必要があります。ほとんどの参照型は、 Equals をオーバーライドする場合でも、等価演算子をオーバーロードしないでください。ただし、複素数型など、値のセマンティクスを持つことを目的とした参照型を実装している場合は、等値演算子をオーバーライドする必要があります。

4

9 に答える 9

26

.NET で等価性を正しく、効率的に、コードの重複なしに実装するのは困難です。具体的には、値セマンティクスを持つ参照型 (つまり、同等性を equality として扱う不変型) の場合System.IEquatable<T>インターフェイスを実装し、すべての異なる操作 ( EqualsGetHashCodeおよび==!=) を実装する必要があります。

例として、値の等価性を実装するクラスを次に示します。

class Point : IEquatable<Point> {
    public int X { get; }
    public int Y { get; }

    public Point(int x = 0, int y = 0) { X = x; Y = y; }

    public bool Equals(Point other) {
        if (other is null) return false;
        return X.Equals(other.X) && Y.Equals(other.Y);
    }

    public override bool Equals(object obj) => Equals(obj as Point);

    public static bool operator ==(Point lhs, Point rhs) => object.Equals(lhs, rhs);

    public static bool operator !=(Point lhs, Point rhs) => ! (lhs == rhs);

    public override int GetHashCode() => X.GetHashCode() ^ Y.GetHashCode();
}

上記のコードで変更可能な部分は、太字の部分 (2 行目Equals(Point other)GetHashCode()メソッド) だけです。他のコードは変更しないでください。

不変値を表さない参照クラスの場合、演算子==and を実装しないでください!=。代わりに、オブジェクトの同一性を比較するというデフォルトの意味を使用してください。

このコードでは、派生クラス型のオブジェクトも意図的に同一視しています。多くの場合、これは望ましくない可能性があります。これは、基本クラスと派生クラスの間の同等性が明確に定義されていないためです。残念ながら、.NET とコーディング ガイドラインはここではあまり明確ではありません。Resharper が作成し、別の回答に投稿されたコードは、このケースを別の方法で処理するEquals(object x)ため、そのような場合に望ましくない動作の影響を受けやすくなります。Equals(SecurableResourcePermission x)

この動作を変更するには、Equals上記の厳密に型指定されたメソッドに追加の型チェックを挿入する必要があります。

public bool Equals(Point other) {
    if (other is null) return false;
    if (other.GetType() != GetType()) return false;
    return X.Equals(other.X) && Y.Equals(other.Y);
}
于 2008-09-19T18:27:11.260 に答える
23

クラスが実装する必要がある Equals というメソッドを持つ C# でコーディングしているようです。「これらの 2 つのポインターは (オブジェクト ハンドルは単なるポインターであるため)同じメモリアドレス?」

ここからサンプルコードをいくつか取得しました:

class TwoDPoint : System.Object
{
    public readonly int x, y;

    public TwoDPoint(int x, int y)  //constructor
    {
        this.x = x;
        this.y = y;
    }

    public override bool Equals(System.Object obj)
    {
        // If parameter is null return false.
        if (obj == null)
        {
            return false;
        }

        // If parameter cannot be cast to Point return false.
        TwoDPoint p = obj as TwoDPoint;
        if ((System.Object)p == null)
        {
            return false;
        }

        // Return true if the fields match:
        return (x == p.x) && (y == p.y);
    }

    public bool Equals(TwoDPoint p)
    {
        // If parameter is null return false:
        if ((object)p == null)
        {
            return false;
        }

        // Return true if the fields match:
        return (x == p.x) && (y == p.y);
    }

    public override int GetHashCode()
    {
        return x ^ y;
    }
}

Java には非常によく似たメカニズムがあります。equals()メソッドはObjectクラスの一部であり、このタイプの機能が必要な場合はクラスでオーバーロードします。

「==」のオーバーロードがオブジェクトにとって悪い考えである理由は、通常、「これらは同じポインターか」比較を実行できるようにしたいからです。これらは通常、たとえば、重複が許可されていないリストに要素を挿入するために依存しており、この演算子が非標準の方法でオーバーロードされている場合、フレームワークの一部が機能しない可能性があります。

于 2008-09-19T18:13:21.217 に答える
16

以下に、IEquatable を実装する際に必要なことをまとめ、MSDN のさまざまなドキュメント ページから理由を説明します。


概要

  • 値が等しいかどうかをテストする必要がある場合 (コレクション内のオブジェクトを使用する場合など)、IEquatable インターフェイスを実装し、クラスの Object.Equals と GetHashCode をオーバーライドする必要があります。
  • 参照の等価性をテストする必要がある場合は、 operator==,operator!= およびObject.ReferenceEqualsを使用する必要があります。
  • ValueTypesと不変の参照型については、operator== と operator!= のみをオーバーライドする必要があります。

正当化

Iquatable

System.IEquatable インターフェイスは、オブジェクトの 2 つのインスタンスが等しいかどうかを比較するために使用されます。オブジェクトは、クラスに実装されたロジックに基づいて比較されます。比較の結果、オブジェクトが異なるかどうかを示すブール値が返されます。これは、オブジェクト値の違いを示す整数を返す System.IComparable インターフェイスとは対照的です。

IEquatable インターフェイスは、オーバーライドする必要がある 2 つのメソッドを宣言します。Equals メソッドには、実際の比較を実行し、オブジェクト値が等しい場合は true を返し、そうでない場合は false を返す実装が含まれています。GetHashCode メソッドは、異なる値を含む同一のオブジェクトを一意に識別するために使用できる一意のハッシュ値を返す必要があります。使用されるハッシュ アルゴリズムのタイプは実装固有です。

IEquatable.Equals メソッド

  • オブジェクトが配列またはジェネリック コレクションに格納される可能性を処理するには、オブジェクトに IEquatable を実装する必要があります。
  • IEquatable を実装する場合は、Object.Equals(Object) および GetHashCode の基本クラスの実装もオーバーライドして、それらの動作が IEquatable.Equals メソッドの動作と一致するようにする必要があります。

Equals() および演算子 == をオーバーライドするためのガイドライン (C# プログラミング ガイド)

  • x.Equals(x) は true を返します。
  • x.Equals(y) は y.Equals(x) と同じ値を返します
  • (x.Equals(y) && y.Equals(z)) が true を返す場合、x.Equals(z) は true を返します。
  • x の連続呼び出し。等号 (y) は、x および y によって参照されるオブジェクトが変更されない限り、同じ値を返します。
  • バツ。Equals (null) は false を返します (null 非許容型の場合のみ。詳細については、Nullable 型 (C# プログラミング ガイド)を参照してください。)
  • Equals の新しい実装では、例外がスローされません。
  • Equals をオーバーライドするクラスは、Object.GetHashCode もオーバーライドすることをお勧めします。
  • Equals(object) の実装に加えて、パフォーマンスを向上させるために、すべてのクラスで独自の型の Equals(type) も実装することをお勧めします。

デフォルトでは、演算子 == は、2 つの参照が同じオブジェクトを示しているかどうかを判断することにより、参照が等しいかどうかをテストします。したがって、参照型は、この機能を得るために operator == を実装する必要はありません。型が不変である場合、つまり、インスタンスに含まれるデータを変更できない場合、演算子 == をオーバーロードして、参照の等価性ではなく値の等価性を比較すると便利です。不変オブジェクトとして、それらは long と同じと見なすことができるからです。値が同じだからです。不変でない型で operator == をオーバーライドすることはお勧めできません。

  • オーバーロードされた operator == 実装は例外をスローすべきではありません。
  • 演算子 == をオーバーロードする型は、演算子 != もオーバーロードする必要があります。

== 演算子 (C# リファレンス)

  • 定義済みの値の型の場合、等価演算子 (==) は、オペランドの値が等しい場合は true を返し、そうでない場合は false を返します。
  • 文字列以外の参照型の場合、2 つのオペランドが同じオブジェクトを参照している場合、== は true を返します。
  • 文字列型の場合、== は文字列の値を比較します。
  • operator== オーバーライド内で == 比較を使用して null をテストする場合は、必ず基本オブジェクト クラス演算子を使用してください。そうしないと、無限再帰が発生し、スタックオーバーフローが発生します。

Object.Equals メソッド (オブジェクト)

プログラミング言語が演算子のオーバーロードをサポートしている場合、および特定の型の等値演算子をオーバーロードすることを選択した場合、その型は Equals メソッドをオーバーライドする必要があります。Equals メソッドのこのような実装は、等値演算子と同じ結果を返す必要があります。

次のガイドラインは、値の型を実装するためのものです。

  • Equals をオーバーライドして、ValueType の Equals の既定の実装によって提供されるパフォーマンスを向上させることを検討してください。
  • Equals をオーバーライドし、言語が演算子のオーバーロードをサポートしている場合は、値の型の等価演算子をオーバーロードする必要があります。

次のガイドラインは、参照型を実装するためのものです。

  • 型のセマンティクスが、型が何らかの値を表すという事実に基づいている場合は、参照型で Equals をオーバーライドすることを検討してください。
  • ほとんどの参照型は、Equals をオーバーライドする場合でも、等価演算子をオーバーロードしてはなりません。ただし、複素数型など、値のセマンティクスを持つことを目的とした参照型を実装している場合は、等値演算子をオーバーライドする必要があります。

追加の落とし穴

于 2009-04-08T16:49:31.980 に答える
3

その記事では、Equals のオーバーライドではなく、等価演算子 (参照型の場合) のオーバーライドを推奨しているだけです。等価チェックが参照チェック以上のものを意味する場合は、オブジェクト (参照または値) 内で Equals をオーバーライドする必要があります。インターフェイスが必要な場合は、IEquatable (ジェネリック コレクションで使用) を実装することもできます。ただし、IEquatable を実装する場合は、IEquatable の注釈セクションに記載されているように、equals もオーバーライドする必要があります。

IEquatable<T> を実装する場合は、Object.Equals(Object) および GetHashCode の基本クラスの実装もオーバーライドして、それらの動作が IEquatable<T>.Equals メソッドの動作と一致するようにする必要があります。Object.Equals(Object) をオーバーライドする場合、オーバーライドされた実装は、クラスの静的 Equals(System.Object, System.Object) メソッドの呼び出しでも呼び出されます。これにより、Equals メソッドのすべての呼び出しが一貫した結果を返すことが保証されます。

Equals および/または等値演算子を実装する必要があるかどうかに関して:

Equals メソッドの実装から

ほとんどの参照型は、Equals をオーバーライドする場合でも、等価演算子をオーバーロードしないでください。

Equals および Equals 演算子 (==) の実装に関するガイドラインから

等価演算子 (==) を実装するたびに Equals メソッドをオーバーライドし、同じことを行うようにします。

これは、等価演算子を実装するたびに Equals をオーバーライドする必要があることを示しているだけです。Equals をオーバーライドするときに等値演算子をオーバーライドする必要があるとは言いません。

于 2008-09-19T18:15:27.687 に答える
2

特定の比較を生成する複雑なオブジェクトの場合は、IComparable を実装し、Compare メソッドで比較を定義するのが適切な実装です。

たとえば、唯一の違いが登録番号である「Vehicle」オブジェクトがあり、これを使用して比較して、テストで返される期待値が必要なものであることを確認します。

于 2008-09-19T18:10:33.110 に答える
1

Microsoft は調整を変更したようです。または、少なくとも等値演算子をオーバーロードしないことについて矛盾する情報があります。このMicrosoft の記事によると、方法: 型の値の等価性を定義する:

「== および != 演算子は、クラスがオーバーロードしていない場合でも、クラスで使用できます。ただし、デフォルトの動作は、参照の等価性チェックを実行することです。クラスで Equals メソッドをオーバーロードする場合は、クラスをオーバーロードする必要があります。 == および != 演算子ですが、必須ではありません。"

Eric Lippert によると、私がC# の等価性のための最小限のコードについて尋ねた質問への回答で、次のように述べています。

「ここで遭遇する危険は、デフォルトで等価性を参照する == 演算子が定義されていることです。オーバーロードされた Equals メソッドが値の等価性を実行し、== が等価性を参照する状況に簡単に陥る可能性があります。値が等しい not-reference-equal に対して誤って参照の等価性を使用する. これはエラーが発生しやすい慣行であり、人間のコードレビューでは見つけるのが困難です.

数年前、この状況を統計的に検出するための静的分析アルゴリズムに取り組んだところ、調査したすべてのコードベースで、コード 100 万行あたり約 2 インスタンスの欠陥率であることがわかりました。どこかで Equals をオーバーライドしたコードベースだけを考えると、欠陥率は明らかにかなり高かったのです!

さらに、コストとリスクを考慮してください。IComparable の実装が既にある場合、すべての演算子を記述することは、バグがなく、変更されることのない簡単なワンライナーです。これは、これまでに作成した中で最も安価なコードです。多数の小さなメソッドを記述してテストするための固定コストと、値の等価性の代わりに参照の等価性が使用されている見にくいバグを見つけて修正するための無限のコストのどちらかを選択できる場合、どちらを選択するかはわかっています。」

.NET Framework は、記述した型で == または != を使用することはありません。しかし、危険なのは、他の誰かがそうしたらどうなるかということです。そのため、クラスがサードパーティ向けである場合は、常に == および != 演算子を提供します。クラスがグループによって内部的にのみ使用されることを意図している場合でも、おそらく == および != 演算子を実装します。

IComparable が実装されている場合は、<、<=、>、および >= 演算子のみを実装します。IComparable は、型が順序付けをサポートする必要がある場合にのみ実装する必要があります。並べ替えを行う場合や、SortedSet などの順序付けられたジェネリック コンテナーで使用する場合などです。

グループまたは会社が == および != 演算子を実装しないというポリシーを定めている場合、もちろんそのポリシーに従います。このようなポリシーが設定されている場合は、参照型で使用されたときに == および != 演算子の出現にフラグを立てる Q/A コード分析ツールを使用してポリシーを適用することが賢明です。

于 2016-09-19T20:02:02.673 に答える
1

Resharper が自動的に作成するものを使用する傾向があります。たとえば、私の参照タイプの1つに対してこれを自動作成しました:

public override bool Equals(object obj)
{
    if (ReferenceEquals(null, obj)) return false;
    if (ReferenceEquals(this, obj)) return true;
    return obj.GetType() == typeof(SecurableResourcePermission) && Equals((SecurableResourcePermission)obj);
}

public bool Equals(SecurableResourcePermission obj)
{
    if (ReferenceEquals(null, obj)) return false;
    if (ReferenceEquals(this, obj)) return true;
    return obj.ResourceUid == ResourceUid && Equals(obj.ActionCode, ActionCode) && Equals(obj.AllowDeny, AllowDeny);
}

public override int GetHashCode()
{
    unchecked
    {
        int result = (int)ResourceUid;
        result = (result * 397) ^ (ActionCode != null ? ActionCode.GetHashCode() : 0);
        result = (result * 397) ^ AllowDeny.GetHashCode();
        return result;
    }
}

オーバーライド==して ref チェックを行いたい場合でも、 を使用できますObject.ReferenceEquals

于 2008-09-19T18:15:37.717 に答える
0

オブジェクトが正しいかどうかをチェックするのと同じくらい簡単なことを実現することは、.NET の設計では少し難しいと思います。

構造体用

1) 実装しIEquatable<T>ます。パフォーマンスが著しく向上します。

2)あなたはEquals今あなた自身を持っているので、オーバーライドGetHashCodeし、さまざまな同等性チェックのオーバーライドと一貫性を保つためにobject.Equals.

3)構造体をorで意図せずに別の構造体と同一視するとコンパイラが警告するため、オーバーロード==!=演算子を宗教的に行う必要はありませんが、メソッドとの一貫性を保つためにそうすることをお勧めします。==!=Equals

public struct Entity : IEquatable<Entity>
{
    public bool Equals(Entity other)
    {
        throw new NotImplementedException("Your equality check here...");
    }

    public override bool Equals(object obj)
    {
        if (obj == null || !(obj is Entity))
            return false;

        return Equals((Entity)obj);
    }

    public static bool operator ==(Entity e1, Entity e2)
    {
        return e1.Equals(e2);
    }

    public static bool operator !=(Entity e1, Entity e2)
    {
        return !(e1 == e2);
    }

    public override int GetHashCode()
    {
        throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
    }
}

授業のために

MS から:

ほとんどの参照型は、Equals をオーバーライドする場合でも、等価演算子をオーバーロードしないでください。

私に==は、値の等価性のように感じられ、Equalsメソッドの構文糖衣のように感じられます。書くa == bことは、書くことよりもはるかに直感的ですa.Equals(b)。参照の等価性をチェックする必要があることはめったにありません。物理オブジェクトの論理表現を扱う抽象レベルでは、これを確認する必要はありません。==と のセマンティクスが異なると、Equals実際には混乱する可能性があると思います。そもそもそれは==価値の平等とEquals参照(または のようなより良い名前)の平等のためであるべきだったと思います。ここでは、MS ガイドラインを真剣に受け止めたくありません。それは、私にとって自然ではないという理由だけでなく、過負荷が大きな害を及ぼさないためでもあります。それは、非ジェネリックをオーバーライドしないのとは異なりますIsSameAs==Equalsまたは、フレームワークはどこにも使用されず、私たち自身が使用する場合にのみ使用さGetHashCodeれるため、噛み付く可能性があります。オーバーロードしない==ことで得られる唯一の本当の利点は、私が制御できないフレームワーク全体の設計との一貫性です。そして、それは確かに大きなことなので、悲しいことに私はそれに固執します.==!=

参照セマンティクス (可変オブジェクト) を使用

1) と をオーバーライドEqualsGetHashCodeます。

2) 実装IEquatable<T>は必須ではありませんが、あると便利です。

public class Entity : IEquatable<Entity>
{
    public bool Equals(Entity other)
    {
        if (ReferenceEquals(this, other))
            return true;

        if (ReferenceEquals(null, other))
            return false;

        //if your below implementation will involve objects of derived classes, then do a 
        //GetType == other.GetType comparison
        throw new NotImplementedException("Your equality check here...");
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as Entity);
    }

    public override int GetHashCode()
    {
        throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
    }
}

値セマンティクス (不変オブジェクト) を使用

これはトリッキーな部分です。気をつけないと簡単にズレてしまいます..

1) と をオーバーライドEqualsGetHashCodeます。

2) オーバーロード==!=を一致させEqualsます。nulls で機能することを確認してください

2) 実装IEquatable<T>は必須ではありませんが、あると便利です。

public class Entity : IEquatable<Entity>
{
    public bool Equals(Entity other)
    {
        if (ReferenceEquals(this, other))
            return true;

        if (ReferenceEquals(null, other))
            return false;

        //if your below implementation will involve objects of derived classes, then do a 
        //GetType == other.GetType comparison
        throw new NotImplementedException("Your equality check here...");
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as Entity);
    }

    public static bool operator ==(Entity e1, Entity e2)
    {
        if (ReferenceEquals(e1, null))
            return ReferenceEquals(e2, null);

        return e1.Equals(e2);
    }

    public static bool operator !=(Entity e1, Entity e2)
    {
        return !(e1 == e2);
    }

    public override int GetHashCode()
    {
        throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
    }
}

クラスを継承できる場合は、特に注意してください。そのような場合、基本クラスのオブジェクトが派生クラスのオブジェクトと等しくなるかどうかを判断する必要があります。理想的には、派生クラスのオブジェクトが等価チェックに使用されない場合、基本クラス インスタンスは派生クラス インスタンスと等価である可能性があり、そのような場合、基本クラスのTypeジェネリックで等価をチェックする必要はありません。Equals

一般に、コードを複製しないように注意してください。再利用を容易にするためのテンプレートとして、一般的な抽象基本クラス (IEqualizable<T>またはそのようなもの) を作成することもできましたが、悲しいことに C# では、追加のクラスから派生することができなくなりました。

于 2012-12-16T20:17:47.890 に答える
0

上記のすべての回答はポリモーフィズムを考慮していません。多くの場合、ベース参照を介して比較した場合でも、派生参照で派生 Equals を使用する必要があります。ここで質問/ディスカッション/回答を参照してください -平等とポリモーフィズム

于 2018-07-02T06:30:32.117 に答える