次の方法で共有


方法: 型の値の等価性を定義する (C# プログラミング ガイド)

クラスまたは構造体を定義する場合は、型の値の等価性 (同値) のカスタム定義を作成することが適切かどうかを判断します。通常、値の等価性を実装するのは、その型のオブジェクトがある種のコレクションに追加されることが予期される場合、または、そのオブジェクトの主な目的が一連のフィールドまたはプロパティを格納することである場合です。値の等価性の定義は、特定の型のすべてのフィールドおよびプロパティに基づくか、特定の型のフィールドおよびプロパティのサブセットに基づくことができます。ただし、いずれの場合も、クラスおよび構造体の両方について、実装は等価性を保証する 5 つの条件に従う必要があります。

  1. x.Equals(x) は true を返します。これは再帰プロパティと呼ばれます。

  2. x.Equals(y) は、y.Equals(x) と同じ値を返します。これは対照プロパティと呼ばれます。

  3. (x.Equals(y) && y.Equals(z)) が true を返す場合、x.Equals(z) も true を返します。これは推移的プロパティと呼ばれます。

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

  5. x.Equals (null) が false を返します。ただし、null.Equals(null) は例外をスローするため、上の 2 番目の規則には従っていないことになります。

独自に定義する構造体には、Object.Equals(Object) メソッドの System.ValueType から値の等価性を継承する既定の実装が既に存在します。 この実装では、リフレクションを使用して、特定の型のパブリック フィールドおよび非パブリック フィールドとプロパティをすべて調べます。この実装では正しい結果が生成されますが、その型専用に記述するカスタム実装と比較すると処理にかなり時間がかかります。

値の等価性に関する実装の詳細は、クラスの場合と構造体の場合で異なります。ただし、クラスと構造体の両方で、等価性の実装に必要な基本手順は同じです。手順は次のとおりです。

  1. 仮想Object.Equals(Object) メソッドをオーバーライドします。ほとんどの場合、bool Equals( object obj ) の実装で必要なのは、System.IEquatable<T> インターフェイスの実装である型固有の Equals メソッドを呼び出すことだけです (手順 2 を参照)。

  2. 型固有の Equals メソッドを指定することによって System.IEquatable<T> インターフェイスを実装します。ここで実際の等価性の比較を実行します。たとえば、型のフィールドを 1 つか 2 つだけ比較することによって等価性を定義できます。Equals から例外をスローしないでください。クラスの場合に限り、このメソッドはクラスで宣言されているフィールドのみを調べます。基本クラスに含まれるフィールドを調べるには、base.Equals を呼び出す必要があります (Object から直接継承された型である場合は、この呼び出しを行わないでください。Object.Equals(Object)Object 実装では参照の等価性チェックが実行されるためです)。

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

  4. Object.GetHashCode をオーバーライドし、値の等価性を持つ 2 つのオブジェクトが同じハッシュ コードを生成するようにします。

  5. 省略可能: "大なり" または "少なり" の定義をサポートするには、型に対して IComparable<T> インターフェイスを実装し、また、<= 演算子および >= 演算子をオーバーロードします。

次に示す最初の例は、クラスの実装です。2 番目の例は構造体の実装を示します。

使用例

次の例は、クラス (参照型) に値の等価性を実装する方法を示します。



    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) メソッドの既定の実装では、参照の等価性の比較を実行し、値の等価性のチェックを実行しません。実装側が仮想メソッドをオーバーライドする場合、仮想メソッドに値の等価性のセマンティクスを提供することが目的です。

== 演算子と != 演算子をオーバーロードしないクラスでも、これらの演算子を使用できます。ただし、既定の動作では参照の等価性のチェックが実行されます。クラスで Equals メソッドをオーバーロードする場合は、== 演算子と != 演算子をオーバーロードすることをお勧めしますが、必須ではありません。

構造体 (値型) に値の等価性を実装する方法を次の例に示します。

    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 でオーバーライドされるバージョン) の既定の実装は、リフレクションを使用して特定の型の全フィールドの値を比較することによって、値の等価性のチェックを実行します。実装側が構造体の Equals 仮想メソッドをオーバーライドする場合、その目的は、値の等価性のチェックをより効率的に実行することと、オプションで構造体のフィールドまたはプロパティの一部のサブセットを比較することです。

== 演算子および != 演算子は、構造体が明示的にこれらの演算子をオーバーロードしない限り構造体を操作できません。

参照

概念

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

その他の技術情報

等価比較 (C# プログラミング ガイド)