方法: 型の値の等価性を定義する (C# プログラミング ガイド)How to: Define Value Equality for a Type (C# Programming Guide)

クラスまたは構造体を定義する場合は、型に値の等価性 (同値) のカスタム定義を作成することが有用かどうかを判断します。When you define a class or struct, you decide whether it makes sense to create a custom definition of value equality (or equivalence) for the type. 通常、値の等価性を実装するのは、その型のオブジェクトがある種のコレクションに追加されることが想定されている場合、または、そのオブジェクトの主な目的が一連のフィールドまたはプロパティを格納することである場合です。Typically, you implement value equality when objects of the type are expected to be added to a collection of some sort, or when their primary purpose is to store a set of fields or properties. 値の等価性は、型のすべてのフィールドおよびプロパティの比較に基づいて定義できます。また、サブセットに基づいて定義することもできます。You can base your definition of value equality on a comparison of all the fields and properties in the type, or you can base the definition on a subset. ただし、いずれの場合も、クラスおよび構造体の両方について、等価性を保証する 5 つの条件に従って実装する必要があります。But in either case, and in both classes and structs, your implementation should follow the five guarantees of equivalence:

  1. x.Equals(x) は true. を返します。これは再帰プロパティと呼ばれます。x.Equals(x) returns true. This is called the reflexive property.

  2. x.Equals(y) は、y.Equals(x) と同じ値を返します。x.Equals(y) returns the same value as y.Equals(x). これは対照プロパティと呼ばれます。This is called the symmetric property.

  3. (x.Equals(y) && y.Equals(z)) が true を返す場合は、x.Equals(z) も true を返します。if (x.Equals(y) && y.Equals(z)) returns true, then x.Equals(z) returns true. これは推移的プロパティと呼ばれます。This is called the transitive property.

  4. x.Equals(y) が連続して呼び出された場合は、x および y によって参照されるオブジェクトが変更されていない限り、同じ値を返します。Successive invocations of x.Equals(y) return the same value as long as the objects referenced by x and y are not modified.

  5. x.Equals(null) は false を返します。x.Equals(null) returns false. ただし、null.Equals(null) は例外をスローするため、上の 2 番目の規則には従っていません。However, null.Equals(null) throws an exception; it does not obey rule number two above.

構造体を定義すると、Object.Equals(Object) メソッドの System.ValueType オーバーライドから継承された値の等価性が既定で実装されます。Any struct that you define already has a default implementation of value equality that it inherits from the System.ValueType override of the Object.Equals(Object) method. この実装では、リフレクションを使用して、型のフィールドとプロパティをすべて調べます。This implementation uses reflection to examine all the fields and properties in the type. この実装によって正しい結果が生成されますが、その型専用に記述したカスタム実装と比較すると、処理にかなり時間がかかります。Although this implementation produces correct results, it is relatively slow compared to a custom implementation that you write specifically for the type.

値の等価性に関する実装の詳細は、クラスと構造体で異なりますが、The implementation details for value equality are different for classes and structs. 等価性を実装するための基本的な手順については、両方とも同じです。However, both classes and structs require the same basic steps for implementing equality:

  1. 仮想 Object.Equals(Object) メソッドをオーバーライドします。Override the virtual Object.Equals(Object) method. ほとんどの場合、bool Equals( object obj ) の実装には、System.IEquatable<T> インターフェイスの実装である型固有の Equals メソッドを呼び出すだけで済みます In most cases, your implementation of bool Equals( object obj ) should just call into the type-specific Equals method that is the implementation of the System.IEquatable<T> interface. (手順 2 を参照)。(See step 2.)

  2. 型固有の Equals メソッドを指定して、System.IEquatable<T> インターフェイスを実装します。Implement the System.IEquatable<T> interface by providing a type-specific Equals method. ここで実際の等価性の比較を実行します。This is where the actual equivalence comparison is performed. たとえば、型のフィールドを 1 ~ 2 個だけ比較することで等価性を定義できます。For example, you might decide to define equality by comparing only one or two fields in your type. Equals から例外をスローしないでください。Do not throw exceptions from Equals. クラスの場合に限り、このメソッドはクラスで宣言されているフィールドのみを調べます。For classes only: This method should examine only fields that are declared in the class. 基底クラスに含まれるフィールドを調べるには、base.Equals を呼び出す必要があります It should call base.Equals to examine fields that are in the base class. (Object から型が直接継承された場合は、この呼び出しを行わないでください。Object.Equals(Object)Object 実装では参照の等価性チェックが実行されるためです)。(Do not do this if the type inherits directly from Object, because the Object implementation of Object.Equals(Object) performs a reference equality check.)

  3. 推奨、ただし省略可能: == 演算子および != 演算子をオーバーロードします。Optional but recommended: Overload the == and != operators.

  4. 値の等価性を持つ 2 つのオブジェクトによって同じハッシュ コードが生成されるように、Object.GetHashCode をオーバーライドします。Override Object.GetHashCode so that two objects that have value equality produce the same hash code.

  5. 省略可能: "大なり" または "小なり" の定義をサポートするには、型に対して IComparable<T> インターフェイスを実装したうえで、<= 演算子および >= 演算子をオーバーロードします。Optional: To support definitions for "greater than" or "less than," implement the IComparable<T> interface for your type, and also overload the <= and >= operators.

次に示す最初の例は、クラスの実装です。The first example that follows shows a class implementation. 2 番目の例は、構造体の実装を示しています。The second example shows a struct implementation.

Example

次の例は、クラス (参照型) で値の等価性を実装する方法を示しています。The following example shows how to implement value equality in a class (reference type).



namespace ValueEquality
{
    using System;
    class TwoDPoint : IEquatable<TwoDPoint>
    {
        // Readonly auto-implemented properties.
        public int X { get; private set; }
        public int Y { get; private set; }

        // Set the properties in the constructor.
        public TwoDPoint(int x, int y)
        {
            if ((x < 1) || (x > 2000) || (y < 1) || (y > 2000))
            {
                throw new System.ArgumentException("Point must be in range 1 - 2000");
            }
            this.X = x;
            this.Y = y;
        }

        public override bool Equals(object obj)
        {
            return this.Equals(obj as TwoDPoint);
        }

        public bool Equals(TwoDPoint p)
        {
            // If parameter is null, return false.
            if (Object.ReferenceEquals(p, null))
            {
                return false;
            }

            // Optimization for a common success case.
            if (Object.ReferenceEquals(this, p))
            {
                return true;
            }

            // If run-time types are not exactly the same, return false.
            if (this.GetType() != p.GetType())
            {
                return false;
            }

            // Return true if the fields match.
            // Note that the base class is not invoked because it is
            // System.Object, which defines Equals as reference equality.
            return (X == p.X) && (Y == p.Y);
        }

        public override int GetHashCode()
        {
            return X * 0x00010000 + Y;
        }

        public static bool operator ==(TwoDPoint lhs, TwoDPoint rhs)
        {
            // Check for null on left side.
            if (Object.ReferenceEquals(lhs, null))
            {
                if (Object.ReferenceEquals(rhs, null))
                {
                    // null == null = true.
                    return true;
                }

                // Only the left side is null.
                return false;
            }
            // Equals handles case of null on right side.
            return lhs.Equals(rhs);
        }

        public static bool operator !=(TwoDPoint lhs, TwoDPoint rhs)
        {
            return !(lhs == rhs);
        }
    }

    // For the sake of simplicity, assume a ThreeDPoint IS a TwoDPoint.
    class ThreeDPoint : TwoDPoint, IEquatable<ThreeDPoint>
    {
        public int Z { get; private set; }

        public ThreeDPoint(int x, int y, int z)
            : base(x, y)
        {
            if ((z < 1) || (z > 2000))
            {
                throw new System.ArgumentException("Point must be in range 1 - 2000");
            }    
            this.Z = z;
        }

        public override bool Equals(object obj)
        {
            return this.Equals(obj as ThreeDPoint);
        }

        public bool Equals(ThreeDPoint p)
        {
            // If parameter is null, return false.
            if (Object.ReferenceEquals(p, null))
            {
                return false;
            }

            // Optimization for a common success case.
            if (Object.ReferenceEquals(this, p))
            {
                return true;
            }

            // Check properties that this class declares.
            if (Z == p.Z)
            {
                // Let base class check its own fields 
                // and do the run-time type comparison.
                return base.Equals((TwoDPoint)p);
            }
            else
            {
                return false;
            }    
        }

        public override int GetHashCode()
        {
            return (X * 0x100000) + (Y * 0x1000) + Z;
        }

        public static bool operator ==(ThreeDPoint lhs, ThreeDPoint rhs)
        {
            // Check for null.
            if (Object.ReferenceEquals(lhs, null))
            {
                if (Object.ReferenceEquals(rhs, null))
                {
                    // null == null = true.
                    return true;
                }

                // Only the left side is null.
                return false;
            }
            // Equals handles the case of null on right side.
            return lhs.Equals(rhs);
        }

        public static bool operator !=(ThreeDPoint lhs, ThreeDPoint rhs)
        {
            return !(lhs == rhs);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ThreeDPoint pointA = new ThreeDPoint(3, 4, 5);
            ThreeDPoint pointB = new ThreeDPoint(3, 4, 5);
            ThreeDPoint pointC = null;
            int i = 5;

            Console.WriteLine("pointA.Equals(pointB) = {0}", pointA.Equals(pointB));
            Console.WriteLine("pointA == pointB = {0}", pointA == pointB);
            Console.WriteLine("null comparison = {0}", pointA.Equals(pointC));
            Console.WriteLine("Compare to some other type = {0}", pointA.Equals(i));

            TwoDPoint pointD = null;
            TwoDPoint pointE = null;



            Console.WriteLine("Two null TwoDPoints are equal: {0}", pointD == pointE);

            pointE = new TwoDPoint(3, 4);
            Console.WriteLine("(pointE == pointA) = {0}", pointE == pointA);
            Console.WriteLine("(pointA == pointE) = {0}", pointA == pointE);
            Console.WriteLine("(pointA != pointE) = {0}", pointA != pointE);

            System.Collections.ArrayList list = new System.Collections.ArrayList();
            list.Add(new ThreeDPoint(3, 4, 5));
            Console.WriteLine("pointE.Equals(list[0]): {0}", pointE.Equals(list[0]));

            // Keep the console window open in debug mode.
            System.Console.WriteLine("Press any key to exit.");
            System.Console.ReadKey();
        }
    }

    /* Output:
        pointA.Equals(pointB) = True
        pointA == pointB = True
        null comparison = False
        Compare to some other type = False
        Two null TwoDPoints are equal: True
        (pointE == pointA) = False
        (pointA == pointE) = False
        (pointA != pointE) = True
        pointE.Equals(list[0]): False
    */
}

クラス (参照型) の場合、両方の Object.Equals(Object) メソッドの既定の実装で、参照の等価性の比較は実行されますが、値の等価性のチェックは実行されません。On classes (reference types), the default implementation of both Object.Equals(Object) methods performs a reference equality comparison, not a value equality check. 実装が仮想メソッドをオーバーライドする場合、その目的は、仮想メソッドに値の等価性のセマンティクスを提供することです。When an implementer overrides the virtual method, the purpose is to give it value equality semantics.

== 演算子と != 演算子は、オーバーロードされなくてもクラスで使用できます。The == and != operators can be used with classes even if the class does not overload them. ただし、既定の動作として参照の等価性のチェックが実行されます。However, the default behavior is to perform a reference equality check. クラスで Equals メソッドをオーバーロードする場合は、== 演算子と != 演算子をオーバーロードすることをお勧めしますが、必須ではありません。In a class, if you overload the Equals method, you should overload the == and != operators, but it is not required.

Example

次の例は、構造体 (値型) で値の等価性を実装する方法を示しています。The following example shows how to implement value equality in a struct (value type):

    struct TwoDPoint : IEquatable<TwoDPoint>
    {
        // Read/write auto-implemented properties.
        public int X { get; private set; }
        public int Y { get; private set; }

        public TwoDPoint(int x, int y)
            : this()
        {
            X = x;
            Y = x;
        }

        public override bool Equals(object obj)
        {
            if (obj is TwoDPoint)
            {
                return this.Equals((TwoDPoint)obj);
            }
            return false;
        }

        public bool Equals(TwoDPoint p)
        {
            return (X == p.X) && (Y == p.Y);
        }

        public override int GetHashCode()
        {
            return X ^ Y;
        }

        public static bool operator ==(TwoDPoint lhs, TwoDPoint rhs)
        {
            return lhs.Equals(rhs);
        }

        public static bool operator !=(TwoDPoint lhs, TwoDPoint rhs)
        {
            return !(lhs.Equals(rhs));
        }
    }


    class Program
    {
        static void Main(string[] args)
        {
            TwoDPoint pointA = new TwoDPoint(3, 4);
            TwoDPoint pointB = new TwoDPoint(3, 4);
            int i = 5;

            // Compare using virtual Equals, static Equals, and == and != operators.
            // True:
            Console.WriteLine("pointA.Equals(pointB) = {0}", pointA.Equals(pointB));
            // True:
            Console.WriteLine("pointA == pointB = {0}", pointA == pointB);
            // True:
            Console.WriteLine("Object.Equals(pointA, pointB) = {0}", Object.Equals(pointA, pointB));
            // False:
            Console.WriteLine("pointA.Equals(null) = {0}", pointA.Equals(null));
            // False:
            Console.WriteLine("(pointA == null) = {0}", pointA == null);
            // True:
            Console.WriteLine("(pointA != null) = {0}", pointA != null);
            // False:
            Console.WriteLine("pointA.Equals(i) = {0}", pointA.Equals(i));
            // CS0019:
            // Console.WriteLine("pointA == i = {0}", pointA == i); 

            // Compare unboxed to boxed.
            System.Collections.ArrayList list = new System.Collections.ArrayList();
            list.Add(new TwoDPoint(3, 4));
            // True:
            Console.WriteLine("pointE.Equals(list[0]): {0}", pointA.Equals(list[0]));


            // Compare nullable to nullable and to non-nullable.
            TwoDPoint? pointC = null;
            TwoDPoint? pointD = null;
            // False:
            Console.WriteLine("pointA == (pointC = null) = {0}", pointA == pointC);
            // True:
            Console.WriteLine("pointC == pointD = {0}", pointC == pointD);

            TwoDPoint temp = new TwoDPoint(3, 4);
            pointC = temp;
            // True:
            Console.WriteLine("pointA == (pointC = 3,4) = {0}", pointA == pointC);

            pointD = temp;
            // True:
            Console.WriteLine("pointD == (pointC = 3,4) = {0}", pointD == pointC);

            // Keep the console window open in debug mode.
            System.Console.WriteLine("Press any key to exit.");
            System.Console.ReadKey();
        }
    }

    /* Output:
        pointA.Equals(pointB) = True
        pointA == pointB = True
        Object.Equals(pointA, pointB) = True
        pointA.Equals(null) = False
        (pointA == null) = False
        (pointA != null) = True
        pointA.Equals(i) = False
        pointE.Equals(list[0]): True
        pointA == (pointC = null) = False
        pointC == pointD = True
        pointA == (pointC = 3,4) = True
        pointD == (pointC = 3,4) = True
    */
}

構造体の場合、Object.Equals(Object) (System.ValueType でオーバーライドされるバージョン) の既定の実装で、リフレクションを使用して値の等価性のチェックが実行され、型のすべてのフィールドの値が比較されます。For structs, the default implementation of Object.Equals(Object) (which is the overridden version in System.ValueType) performs a value equality check by using reflection to compare the values of every field in the type. 実装が構造体の仮想 Equals メソッドをオーバーライドする場合、その目的は、値の等価性のチェックをより効率的に実行することと、オプションで、構造体のフィールドまたはプロパティの一部のサブセットに基づいて比較を行うことです。When an implementer overrides the virtual Equals method in a struct, the purpose is to provide a more efficient means of performing the value equality check and optionally to base the comparison on some subset of the struct's field or properties.

== 演算子および != 演算子は、構造体が明示的にその演算子をオーバーロードしない限り、その構造体を操作できません。The == and != operators cannot operate on a struct unless the struct explicitly overloads them.

参照See Also

等価比較Equality Comparisons
C# プログラミング ガイドC# Programming Guide