Was ist der Unterschied zwischen IEqualityComparer <T> und IEquatable <T>?

151

Ich möchte die Szenarien verstehen, in denen IEqualityComparer<T>und wo IEquatable<T>sie verwendet werden sollten. Die MSDN-Dokumentation für beide sieht sehr ähnlich aus.

Tilak
quelle
1
MSDN sez: "Diese Schnittstelle ermöglicht die Implementierung eines benutzerdefinierten Gleichheitsvergleichs für Sammlungen. " Dies wird natürlich in der ausgewählten Antwort abgeleitet. MSDN empfiehlt auch EqualityComparer<T>, die Schnittstelle zu erben, anstatt sie zu implementieren, "weil die EqualityComparer<T>Gleichheit mitIEquatable<T>
radarbob
... das oben Gesagte schlägt vor, dass ich für jede TImplementierung eine benutzerdefinierte Sammlung erstellen sollte IEquatable<T>. Hätte eine Sammlung List<T>sonst gerne einen subtilen Fehler?
Radarbob
gute Erklärung anotherchris.net/csharp/…
Ramil Shavaleev
@RamilShavaleev der Link ist unterbrochen
ChessMax

Antworten:

117

IEqualityComparer<T>ist eine Schnittstelle für ein Objekt, die den Vergleich für zwei Objekte des Typs durchführt T.

IEquatable<T>ist für ein Objekt vom Typ, Tdamit es sich mit einem anderen Objekt des gleichen Typs vergleichen kann.

Justin Niessner
quelle
1
Der gleiche Unterschied besteht zwischen IComparable / IComparer
Boctulus
3
Kapitän offensichtlich
Stepan Ivanenko
(Edited) IEqualityComparer<T>ist eine Schnittstelle für ein Objekt (die in der Regel ist eine leichte Klasse unterschiedlich T) , die Vergleichsfunktionen bereitstellt , die auf betreibtT
rwong
61

Bei der Entscheidung, ob IEquatable<T>oder verwendet werden soll IEqualityComparer<T>, könnte man fragen:

Gibt es eine bevorzugte Methode zum Testen von zwei Instanzen Tauf Gleichheit oder gibt es mehrere gleichermaßen gültige Methoden?

  • Wenn es nur eine Möglichkeit gibt, zwei Instanzen Tauf Gleichheit zu testen , oder wenn eine von mehreren Methoden bevorzugt wird, ist IEquatable<T>dies die richtige Wahl: Diese Schnittstelle sollte nur für sich Tselbst implementiert werden , damit eine Instanz Tüber internes Wissen verfügt wie man sich mit einer anderen Instanz von vergleicht T.

  • Auf der anderen Seite erscheint es angemessener, wenn es mehrere gleichermaßen vernünftige Methoden zum Vergleichen von zwei Ts auf Gleichheit gibt IEqualityComparer<T>: Diese Schnittstelle soll nicht für sich selbst implementiert werden T, sondern für andere "externe" Klassen. Wenn Sie zwei Instanzen Tauf Gleichheit testen T, müssen Sie daher eine explizite Auswahl einer IEqualityComparer<T>Instanz treffen, die den Test gemäß Ihren spezifischen Anforderungen ausführt , da kein internes Verständnis der Gleichheit vorliegt.

Beispiel:

Betrachten wir diese beiden Typen (die angeblich haben Wertesemantik ):

interface IIntPoint : IEquatable<IIntPoint>
{
    int X { get; }
    int Y { get; }
}

interface IDoublePoint  // does not inherit IEquatable<IDoublePoint>; see below.
{
    double X { get; }
    double Y { get; }
}

Warum sollte nur einer dieser Typen erben IEquatable<>, der andere nicht?

Theoretisch gibt es nur einen sinnvollen Weg, zwei Instanzen beider Typen zu vergleichen: Sie sind gleich, wenn die Eigenschaften Xund Yin beiden Instanzen gleich sind. Nach dieser Überlegung sollten beide Typen implementiert werden IEquatable<>, da es nicht wahrscheinlich ist, dass es andere sinnvolle Möglichkeiten gibt, einen Gleichheitstest durchzuführen.

Das Problem hierbei ist, dass der Vergleich von Gleitkommazahlen auf Gleichheit aufgrund winziger Rundungsfehler möglicherweise nicht wie erwartet funktioniert . Es gibt verschiedene Methoden zum Vergleichen von Gleitkommazahlen auf nahezu Gleichheit mit jeweils spezifischen Vorteilen und Kompromissen. Möglicherweise möchten Sie selbst auswählen können, welche Methode geeignet ist.

sealed class DoublePointNearEqualityComparerByTolerance : IEqualityComparer<IDoublePoint>
{
    public DoublePointNearEqualityComparerByTolerance(double tolerance) {  }
    
    public bool Equals(IDoublePoint a, IDoublePoint b)
    {
        return Math.Abs(a.X - b.X) <= tolerance  &&  Math.Abs(a.Y - b.Y) <= tolerance;
    }
    
}

Beachten Sie, dass auf der Seite, auf die ich (oben) verwiesen habe, ausdrücklich angegeben ist, dass dieser Test auf nahezu Gleichheit einige Schwächen aufweist. Da dies eine IEqualityComparer<T>Implementierung ist, können Sie sie einfach austauschen, wenn sie für Ihre Zwecke nicht gut genug ist.

stakx - nicht mehr beitragen
quelle
6
Die vorgeschlagene Vergleichsmethode zum Testen der Doppelpunktgleichheit ist fehlerhaft, da jede legitime IEqualityComparer<T>Implementierung eine Äquivalenzbeziehung implementieren muss , was bedeutet, dass in allen Fällen, in denen zwei Objekte gleich einem Drittel sind, sie gleich miteinander verglichen werden müssen . Jede Klasse, die das oben Gesagte implementiert IEquatable<T>oder IEqualityComparer<T>auf eine Weise widerspricht, ist gebrochen.
Supercat
5
Ein besseres Beispiel wären Vergleiche zwischen Zeichenfolgen. Es ist sinnvoll, eine Zeichenfolgenvergleichsmethode zu haben, die Zeichenfolgen nur dann als gleich betrachtet, wenn sie dieselbe Folge von Bytes enthalten. Es gibt jedoch auch andere nützliche Vergleichsmethoden, die auch Äquivalenzbeziehungen darstellen , z. B. Vergleiche ohne Berücksichtigung der Groß- und Kleinschreibung. Beachten Sie, dass a, bei IEqualityComparer<String>dem "Hallo" gleich "Hallo" und "hElLo" ist, "Hallo" und "hElLo" gleich betrachten muss, aber für die meisten Vergleichsmethoden wäre dies kein Problem.
Supercat
@supercat, danke für das wertvolle Feedback. Es ist wahrscheinlich, dass ich in den nächsten Tagen nicht dazu komme, meine Antwort zu überarbeiten. Wenn Sie möchten, können Sie diese nach Belieben bearbeiten.
stakx - nicht mehr beitragen
Genau das brauchte ich und was ich tue. GetHashCode muss jedoch überschrieben werden, wobei false zurückgegeben wird, sobald die Werte unterschiedlich sind. Wie gehst du mit deinem GetHashCode um?
Teapeng
@teapeng: Ich bin mir nicht sicher, über welchen speziellen Anwendungsfall Sie sprechen. Im Allgemeinen GetHashCodesollten Sie schnell feststellen können, ob sich zwei Werte unterscheiden. Die Regeln lauten ungefähr wie folgt: (1) GetHashCode muss immer den gleichen Hashcode für den gleichen Wert erzeugen. (2) GetHashCode sollte schnell sein (schneller als Equals). (3) GetHashCode muss nicht präzise sein (nicht so präzise wie Equals). Dies bedeutet, dass möglicherweise der gleiche Hashcode für verschiedene Werte erzeugt wird. Je präziser Sie es machen können, desto besser, aber es ist wahrscheinlich wichtiger, es schnell zu halten.
stakx - nicht mehr
27

Sie haben bereits die grundlegende Definition dessen, was sie sind . Kurz gesagt, wenn Sie eine IEquatable<T>Klasse implementieren T, gibt die EqualsMethode für ein Objekt vom Typ an T, ob das Objekt selbst (das auf Gleichheit getestete) einer anderen Instanz desselben Typs entspricht T. Dient IEqualityComparer<T>zum Testen der Gleichheit von zwei beliebigen Instanzen von T, typischerweise außerhalb des Geltungsbereichs der Instanzen von T.

In Bezug auf was sie sind für verwirrend auf den ersten sein. Aus der Definition sollte klar sein, dass daher IEquatable<T>(in der Klasse Tselbst definiert) der De-facto-Standard sein sollte, um die Eindeutigkeit seiner Objekte / Instanzen darzustellen. HashSet<T>, Dictionary<T, U>(unter Berücksichtigung GetHashCodewird auch überschrieben), Containsauf List<T>etc nutzen Sie dies. Das Implementieren IEqualityComparer<T>auf Thilft den oben genannten allgemeinen Fällen nicht. In der Folge gibt es wenig Wert für die Implementierung IEquatable<T>in einer anderen Klasse als T. Dies:

class MyClass : IEquatable<T>

macht selten Sinn.

Andererseits

class T : IEquatable<T>
{
    //override ==, !=, GetHashCode and non generic Equals as well

    public bool Equals(T other)
    {
        //....
    }
}

ist, wie es gemacht werden sollte.

IEqualityComparer<T>kann nützlich sein, wenn Sie eine benutzerdefinierte Validierung der Gleichheit benötigen, jedoch nicht in der Regel. Zum Beispiel müssen Sie in einer Klasse von Personirgendwann die Gleichheit von zwei Personen anhand ihres Alters testen. In diesem Fall können Sie Folgendes tun:

class Person
{
    public int Age;
}

class AgeEqualityTester : IEqualityComparer<Person>
{
    public bool Equals(Person x, Person y)
    {
        return x.Age == y.Age;
    }

    public int GetHashCode(Person obj)
    {
        return obj.Age.GetHashCode;
    }
}

Versuchen Sie es, um sie zu testen

var people = new Person[] { new Person { age = 23 } };
Person p = new Person() { age = 23 };

print people.Contains(p); //false;
print people.Contains(p, new AgeEqualityTester()); //true

Ebenso macht IEqualityComparer<T>on Tkeinen Sinn.

class Person : IEqualityComparer<Person>

Das funktioniert zwar, sieht aber für die Augen nicht gut aus und besiegt die Logik.

Normalerweise brauchen Sie IEquatable<T>. Idealerweise können Sie auch nur eine haben, IEquatable<T>während mehrere IEqualityComparer<T>nach verschiedenen Kriterien möglich sind.

Die IEqualityComparer<T>und IEquatable<T>sind genau analog zu Comparer<T>und IComparable<T>werden zu Vergleichszwecken verwendet, anstatt sie gleichzusetzen. Ein guter Thread hier, wo ich die gleiche Antwort geschrieben habe :)

nawfal
quelle
public int GetHashCode(Person obj)sollte zurückkehrenobj.GetHashCode()
Hyankov
@HristoYankov sollte zurückkehren obj.Age.GetHashCode. wird bearbeiten.
Nawfal
Bitte erläutern Sie, warum der Vergleicher eine solche Annahme darüber treffen muss, was der Hash des zu vergleichenden Objekts ist. Ihr Vergleicher weiß nicht, wie Person seinen Hash berechnet. Warum sollten Sie das überschreiben?
Hyankov
@HristoYankov Ihr Vergleicher weiß nicht, wie Person seinen Hash berechnet - Sicher, deshalb haben wir person.GetHashCodenirgendwo direkt angerufen ... warum sollten Sie das überschreiben? - Wir überschreiben, weil IEqualityCompareres darum geht, eine andere Vergleichsimplementierung gemäß unseren Regeln durchzuführen - den Regeln, die wir wirklich gut kennen.
Nawfal
In meinem Beispiel muss ich meinen Vergleich auf die AgeEigenschaft stützen , also rufe ich an Age.GetHashCode. Das Alter ist vom Typ int, aber was auch immer der Typ ist, der Hash-Code-Vertrag in .NET ist das different objects can have equal hash but equal objects cant ever have different hashes. Dies ist ein Vertrag, an den wir blind glauben können. Natürlich sollten auch unsere benutzerdefinierten Vergleicher diese Regel einhalten. Wenn jemals ein Aufruf someObject.GetHashCodekaputt geht, dann ist es das Problem mit Implementierern vom Typ someObject, es ist nicht unser Problem.
Nawfal
11

IEqualityComparer wird verwendet, wenn die Gleichheit zweier Objekte extern implementiert wird, z. B. wenn Sie einen Vergleicher für zwei Typen definieren möchten, für die Sie keine Quelle hatten, oder wenn die Gleichheit zweier Dinge nur in einem begrenzten Kontext sinnvoll ist.

IEquatable ist für das Objekt selbst (das Objekt, das auf Gleichheit verglichen wird) zu implementieren.

Chris Shain
quelle
Sie sind der einzige, der das Problem "Plugging in Equality" (externe Verwendung) angesprochen hat. plus 1.
Royi Namir
4

Man vergleicht zwei Ts. Der andere kann sich mit anderen vergleichen T. Normalerweise müssen Sie jeweils nur einen verwenden, nicht beide.

Matt Ball
quelle