Equals() と演算子 == のオーバーロードに関するガイドライン (C# プログラミング ガイド)

更新 : 2007 年 11 月

C# には、2 種類の等価があります。1 つは参照の等価で、もう 1 つは値の等価です。値が等価であるとは、一般的に理解されている意味での等価で、2 つのオブジェクトが同じ値を含んでいることを意味します。たとえば、2 という値を持つ 2 つの整数は値が等価です。一方、参照が等価であるとは、比較する 2 つのオブジェクトが存在しないことを意味します。この場合は、オブジェクトの代わりに、共に同じオブジェクトを参照する 2 つのオブジェクト参照が存在します。次の例に示すように、これは簡単な代入によって生じます。

System.Object a = new System.Object();
System.Object b = a;
System.Object.ReferenceEquals(a, b);  //returns true

このコードでは、オブジェクトは 1 つだけですが、そのオブジェクトへの参照は、a、b と複数あります。これらはいずれも同じオブジェクトを参照するので、参照が等価です。2 つのオブジェクトの参照が等価であれば、それらの値も等価になりますが、値が等価であるからといって、必ずしも参照が等価になるとは限りません。

参照が等価であることを確認するには、ReferenceEquals を使用します。値が等価であることを確認するには、Equals を使用します。

Equals のオーバーライド

Equals は仮想メソッドであるため、任意のクラスで実装をオーバーライドできます。値を表すクラス、実質的にあらゆる値型を表すクラス、または値のセットをグループとして表すクラス (複素数クラスなど) では、Equals をオーバーライドする必要があります。型が IComparable を実装する場合は、Equals をオーバーライドする必要があります。

Equals の新しい実装は、Equals が保証する以下の事項をすべて踏襲する必要があります。

  • x.Equals(x) は true を返します。

  • x. Equals (y) は、y. Equals (x) と同じ値を返します。

  • (x. Equals (y) && y. Equals (z)) が true を返す場合、x. Equals (z) も true を返します。

  • x. Equals (y) が連続して呼び出された場合は、x および y によって参照されるオブジェクトが変更されていない限り、同じ値を返します。

  • x. Equals (null) は false を返します (null 非許容値型の場合のみ)。詳細については、「null 許容型 (C# プログラミング ガイド)」を参照してください。

Equals の新しい実装は、例外をスローできません。Equals をオーバーライドするクラスでは、Object.GetHashCode もオーバーライドすることをお勧めします。また、Equals (オブジェクト) を実装するだけでなく、パフォーマンスを向上するために、固有の型に対する Equals (型) をすべてのクラスで実装することもお勧めします。次に例を示します。

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;
    }
}

基本クラスの Equals を呼び出すことができる派生クラスでは、比較を完了する前に呼び出す必要があります。次の例では、Equals で基本クラスの Equals を呼び出し、null パラメータを確認して、パラメータの型と派生クラスの型を比較します。これにより、派生クラスの Equals の実装に、派生クラスで宣言された新しいデータ フィールドをチェックするタスクが委ねられます。

class ThreeDPoint : TwoDPoint
{
    public readonly int z;

    public ThreeDPoint(int x, int y, int z)
        : base(x, y)
    {
        this.z = z;
    }

    public override bool Equals(System.Object obj)
    {
        // If parameter cannot be cast to ThreeDPoint return false:
        ThreeDPoint p = obj as ThreeDPoint;
        if ((object)p == null)
        {
            return false;
        }

        // Return true if the fields match:
        return base.Equals(obj) && z == p.z;
    }

    public bool Equals(ThreeDPoint p)
    {
        // Return true if the fields match:
        return base.Equals((TwoDPoint)p) && z == p.z;
    }

    public override int GetHashCode()
    {
        return base.GetHashCode() ^ z;
    }
}

演算子 == のオーバーライド

既定では、演算子 == は、2 つの参照が同じオブジェクトを示すかどうかを確認して参照の等価をテストします。したがって、この機能を取得するために参照型で演算子 == を実装する必要はありません。型が変更できない場合、つまりインスタンスに含まれているデータを変更できない場合、その型は、変更不可能なオブジェクトとして、同じ値を持つ限り同一と見なされるので、参照の等価の代わりに値の等価を比較するように演算子 == をオーバーロードするのが有効です。変更不可能な型以外で演算子 == をオーバーライドすることはお勧めしません。

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

//add this code to class ThreeDPoint as defined previously
//
public static bool operator ==(ThreeDPoint a, ThreeDPoint b)
{
    // If both are null, or both are same instance, return true.
    if (System.Object.ReferenceEquals(a, b))
    {
        return true;
    }

    // If one is null, but not both, return false.
    if (((object)a == null) || ((object)b == null))
    {
        return false;
    }

    // Return true if the fields match:
    return a.x == b.x && a.y == b.y && a.z == b.z;
}

public static bool operator !=(ThreeDPoint a, ThreeDPoint b)
{
    return !(a == b);
}
ms173147.alert_note(ja-jp,VS.90).gifメモ :

演算子 == のオーバーロードでは、(a == b)、(a == null)、または (b == null) を使用して参照が等価であることを確認するというエラーがよくあります。このような場合は、オーバーロードされた演算子 == が呼び出され、無限ループが生じます。無限ループを避けるには、ReferenceEquals を使用するか、型を Object にキャストしてください。

参照

概念

C# プログラミング ガイド

参照

ステートメント、式、および演算子 (C# プログラミング ガイド)

演算子 (C# プログラミング ガイド)

オーバーロードされた演算子 (C# プログラミング ガイド)

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

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