Porady: definiowanie równości wartości dla typu (Przewodnik programowania w języku C#)

Podczas definiowania klasy lub struktury zdecydować czy sensowne do utworzyć niestandardowych definicji równości wartość (lub równoważności) dla danego typu.Zazwyczaj zaimplementowaniem równość wartości mają zostać dodane do kolekcji jakiegoś oczekuje obiektów typu lub gdy ich głównym celem jest przechowywać zestaw pól lub właściwości.Definicja, równość wartości można oprzeć na porównanie pola i właściwości typu lub można oprzeć definicję na podzbiorze.Jednak w każdym przypadku i zarówno klas, jak i strukturach implementacji należy postępować zgodnie z pięciu gwarancje równoważności:

  1. x.Equals(x) zwraca true. jest to tak zwane zwrotnej właściwość.

  2. x.Equals(y) zwraca taką samą wartość jak y.Equals(x).Jest to nazywane symetrycznego właściwość.

  3. Jeśli (x.Equals(y) & & y.Equals(z)) returns true, then x.Equals(z) returns true.Jest to nazywane przechodnie właściwość.

  4. Kolejne wywołania x.Equals(y) return taką samą wartość jak długo obiekty odwołuje się x i y nie są modyfikowane.

  5. x.Equals(null) returns false.Jednakże null.Equals(NULL) zgłasza wyjątek; to nie stosuj numer reguły dwóch powyższych.

Wszelkie struct definiujących już została domyślna implementacja równości wartość, która dziedziczy z ValueType zastąpić z Object.Equals(Object) metoda. Ta implementacja używa odbicie badania publicznych i niepublicznych pól i właściwości typu.Chociaż ta implementacja produkuje poprawnych wyników, jest stosunkowo powolne w porównaniu do niestandardowej implementacji, napisany specjalnie dla danego typu.

Szczegóły dotyczące implementacji dla wartości równości są różne dla klas i strukturach.Jednakże zarówno klas, jak i strukturach wymagają te same podstawowe kroki wykonywania równości:

  1. Zastąpić wirtualnegoObject.Equals(Object)metoda. W większości przypadków implementacji bool Equals( object obj ) po prostu należy zadzwonić do określonego typu Equals metoda wdrażania jest IEquatable interfejs. (Zobacz krok 2).

  2. Wdrożenie IEquatable interfejs dostarczając określonego typu Equals metoda. Jest to, gdzie wykonywane jest porównanie rzeczywistych równoważności.Na przykład można zdecydować zdefiniować równości poprzez porównanie tylko jednego lub dwóch pól w sieci typu.Nie generują wyjątki od Equals.Dla klas tylko: ta metoda powinna zbadać tylko te pola, które są zadeklarowane w klasie.Powinna wywołać base.Equals do zbadania pól, które znajdują się w klasie podstawowej.(Nie należy tego robić, jeśli typ dziedziczy bezpośrednio z Object, ponieważ Object wykonania Object.Equals(Object) wykonuje sprawdzanie równości odwołanie.)

  3. Opcjonalne, ale zalecane: przeciążenie == i ! = podmiotów gospodarczych.

  4. Zastąpić Object.GetHashCode tak, że dwa obiekty, które mają wartość równości produkuje ten sam wartość skrótu.

  5. Opcjonalnie: Aby obsługiwać definicje "większy niż" lub "mniejszy niż", zaimplementować IComparable interfejs użytkownika typu, a także przeładowanie < = i > = podmiotów gospodarczych.

W pierwszym przykładzie, który następuje przedstawiono Implementacja klasy.Drugi przykład przedstawia wykonania struct.

Przykład

Poniższy przykład pokazuje, jak zaimplementować wartość równości w klasie (typ referencyjny).

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
        */
    }

Na klasy (typy odwołań) Domyślna implementacja obu Object.Equals(Object) metody wykonuje porównanie równości odniesienia nie wyboru równość wartości.Gdy Realizator zastępuje wirtualna metoda, celem jest nadaj wartość równości semantyka.

== i != operatorów może być używany z klas, nawet jeśli klasa nie przeładowanie je.Jednak zachowanie domyślne jest sprawdzać równości odniesienia.W klasie Jeśli się przeładowanie Equals metodapowinna się przeładowanie == i != operatorów, ale nie jest wymagane.

Poniższy przykład pokazuje, jak zaimplementować wartość równości w struct (typ wartości):

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
    */
}

Dla struktur, domyślna implementacja z Object.Equals(Object) (która jest zastąpiona wersja w ValueType) wykonuje sprawdzanie równości wartości przy użyciu odbicie na porównywaniu wartości każdego pole typu.Kiedy Realizator zastępuje wirtualnego Equals metoda w stuct celem jest zapewnienie bardziej skuteczne środki wykonywania wartość wyboru równości i opcjonalnie podstawę porównania podzestawu struct pole lub właściwości.

== i ! = operatorów nie może działać na struct, chyba że struktura overloads wyraźnie je.

Zobacz też

Koncepcje

Przewodnik programowania w języku C#

Inne zasoby

Porównywanie równości (Przewodnik programowania w języku C#)