Gewusst wie: Definieren von Wertgleichheit für einen Typ (C#-Programmierhandbuch)How to: Define Value Equality for a Type (C# Programming Guide)

Wenn Sie eine Klasse oder Struktur definieren, entscheiden Sie, ob es sinnvoll ist, eine benutzerdefinierte Definition der Wertgleichheit (oder Äquivalenz) für den Typ zu erstellen.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. In der Regel implementieren Sie Wertgleichheit, wenn Objekte des Typs zu einer Auflistung hinzugefügt werden sollen oder wenn ihr Hauptzweck im Speichern einer Reihe von Feldern oder Eigenschaften besteht.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. Sie können die Definition der Wertgleichheit auf einem Vergleich aller Felder und Eigenschaften im Typ oder auf einer Teilmenge aufbauen.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. Ihre Implementierung in Klassen und Strukturen sollte in beiden Fällen die fünf Äquivalenzgarantien befolgen:But in either case, and in both classes and structs, your implementation should follow the five guarantees of equivalence:

  1. xEquals(x) gibt true. zurück. Dies wird als reflexive Eigenschaft bezeichnet.x.Equals(x) returns true. This is called the reflexive property.

  2. x.Equals(y) gibt denselben Wert wie y.Equals(x) zurück.x.Equals(y) returns the same value as y.Equals(x). Die wird als symmetrische Eigenschaft bezeichnet.This is called the symmetric property.

  3. Wenn (x.Equals(y) && y.Equals(z)) true zurückgibt, dann gibt x.Equals(z) true zurück.if (x.Equals(y) && y.Equals(z)) returns true, then x.Equals(z) returns true. Dies wird als transitive Eigenschaft bezeichnet.This is called the transitive property.

  4. Aufeinander folgende Aufrufe von x.Equals(y) geben immer denselben Wert zurück, es sei denn, die Objekte, auf die x und y verweisen, werden geändert.Successive invocations of x.Equals(y) return the same value as long as the objects referenced by x and y are not modified.

  5. xEquals(null) gibt false zurück.x.Equals(null) returns false. null.Equals(null) löst allerdings eine Ausnahme aus, da es Regel Nummer 2 oben nicht befolgt.However, null.Equals(null) throws an exception; it does not obey rule number two above.

Jede Struktur, die Sie definieren, besitzt bereits eine Standardimplementierung der Wertgleichheit, die von der System.ValueType-Außerkraftsetzung der Object.Equals(Object)-Methode geerbt wurde.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. Diese Implementierung verwendet Reflektion, um alle Felder und Eigenschaften im Typ zu untersuchen.This implementation uses reflection to examine all the fields and properties in the type. Obwohl diese Implementierung richtige Ergebnisse produziert, ist sie relativ langsam im Vergleich zu einer benutzerdefinierten Implementierung, die Sie speziell für den Typ schreiben.Although this implementation produces correct results, it is relatively slow compared to a custom implementation that you write specifically for the type.

Die Implementierungsdetails für Wertgleichheit unterscheiden sich für Klassen und Strukturen.The implementation details for value equality are different for classes and structs. Sowohl Klassen als auch Strukturen erfordern dieselben grundlegenden Schritte zur Implementierung der Gleichheit:However, both classes and structs require the same basic steps for implementing equality:

  1. Überschreiben Sie die virtuelle Methode Object.Equals(Object).Override the virtual Object.Equals(Object) method. In den meisten Fällen sollte Ihre Implementierung von bool Equals( object obj ) nur die typspezifische Equals-Methode aufrufen, die die Implementierung der Schnittstelle System.IEquatable<T> darstellt.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. (Siehe Schritt 2)(See step 2.)

  2. Implementieren Sie die Schnittstelle System.IEquatable<T>, indem Sie eine typspezifische Equals-Methode bereitstellen.Implement the System.IEquatable<T> interface by providing a type-specific Equals method. An dieser Stelle wird der eigentliche Äquivalenzvergleich ausgeführt.This is where the actual equivalence comparison is performed. Sie könnten Gleichheit z.B. nur über den Vergleich von ein oder zwei Feldern in Ihrem Typ definieren.For example, you might decide to define equality by comparing only one or two fields in your type. Lösen Sie keine Ausnahmen aus Equals aus.Do not throw exceptions from Equals. Nur für Klassen: Diese Methode sollte nur Felder prüfen, die in der Klasse deklariert sind.For classes only: This method should examine only fields that are declared in the class. Sie sollte base.Equals aufrufen, um Felder in der Basisklasse zu prüfen.It should call base.Equals to examine fields that are in the base class. (Tun Sie dies nicht, wenn der Typ direkt von Object erbt, da die Object-Implementierung von Object.Equals(Object) eine Überprüfung der Verweisgleichheit durchführt.)(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, aber empfohlen: Überladen Sie die Operatoren == und !=.Optional but recommended: Overload the == and != operators.

  4. Überschreiben Sie Object.GetHashCode, damit zwei Objekte, die Wertgleichheit besitzen, den gleichen Hashcode erzeugen.Override Object.GetHashCode so that two objects that have value equality produce the same hash code.

  5. Optional: Implementieren Sie zur Unterstützung von Definitionen für „größer als“ oder „kleiner als“ die Schnittstelle IComparable<T> für Ihren Typ, und überladen Sie die Operatoren <= und >=.Optional: To support definitions for "greater than" or "less than," implement the IComparable<T> interface for your type, and also overload the <= and >= operators.

Das erste nachfolgende Beispiel zeigt die Implementierung einer Klasse.The first example that follows shows a class implementation. Das zweite nachfolgende Beispiel zeigt die Implementierung einer Struktur.The second example shows a struct implementation.

BeispielExample

Im folgenden Beispiel wird veranschaulicht, wie Sie Wertgleichheit in einer Klasse (Referenztyp) implementieren.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
    */
}

Die standardmäßige Implementierung beider Object.Equals(Object)-Methoden führt bei Klassen (Referenztypen) einen Verweisgleichheitsvergleich, keine Wertgleichheitsprüfung aus.On classes (reference types), the default implementation of both Object.Equals(Object) methods performs a reference equality comparison, not a value equality check. Wenn eine Implementierer die virtuelle Methode überschreibt, dient dies dazu, ihr Wertgleichheitssemantik zu verleihen.When an implementer overrides the virtual method, the purpose is to give it value equality semantics.

Die Operatoren == und != können mit Klassen verwendet werden, selbst wenn die Klasse sie nicht überlädt.The == and != operators can be used with classes even if the class does not overload them. Allerdings ist das Standardverhalten eine Ausführung einer Verweisgleichheitsprüfung.However, the default behavior is to perform a reference equality check. Wenn Sie die Equals-Methode in einer Klasse überladen, sollten Sie die Operatoren == und != überladen, obwohl dies nicht erforderlich ist.In a class, if you overload the Equals method, you should overload the == and != operators, but it is not required.

BeispielExample

Im folgenden Beispiel wird veranschaulicht, wie Sie Wertgleichheit in einer Struktur (Werttyp) implementieren: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
    */
}

Für Strukturen führt die Standardimplementierung von Object.Equals(Object) (was die außer Kraft gesetzte Version in System.ValueType ist) eine Überprüfung der Wertgleichheit durch, indem sie Reflektion zum Vergleichen der Werte jedes Felds im Typ verwendet.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. Wenn ein Implementierer die virtuelle Equals-Methode in einer Struktur überschreibt, dient dies der Bereitstellung effizienterer Mittel für die Ausführung der Wertgleichheitsprüfung und optional dazu, den Vergleich auf einer Teilmenge des Felds oder der Eigenschaften der Struktur zu basieren.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.

Die Operatoren == und != können nicht auf Strukturen operieren, es sei denn, die Struktur überlädt sie explizit.The == and != operators cannot operate on a struct unless the struct explicitly overloads them.

Siehe auchSee Also

ÜbereinstimmungsvergleicheEquality Comparisons
C#-ProgrammierhandbuchC# Programming Guide