2015-02-10 9 views
20

Ich bin mir der Tatsache bewusst, dass ich immer Equals(object) und GetHashCode() bei der Implementierung IEquatable<T>.Equals(T) übersteuern muss.Warum gewinnt Equals (Objekt) Equals (T), wenn ein geerbtes Objekt in Hashset oder anderen Auflistungen verwendet wird?

Allerdings verstehe ich nicht, warum in einigen Situationen die Equals(object) gewinnt über die generische Equals(T).

Zum Beispiel warum passiert folgendes? Wenn ich für eine Schnittstelle IEquatable<T> deklariere und dafür einen konkreten Typ X implementiere, wird der generelle Equals(object) durch einen Hashset<X> aufgerufen, wenn solche Items miteinander verglichen werden. In allen anderen Situationen, in denen mindestens eine der Seiten in das Interface umgewandelt wurde, wird das korrekte Equals(T) aufgerufen.

Hier ist ein Code-Beispiel zu demonstrieren:

public interface IPerson : IEquatable<IPerson> { } 

//Simple example implementation of Equals (returns always true) 
class Person : IPerson 
{ 
    public bool Equals(IPerson other) 
    { 
     return true; 
    } 

    public override bool Equals(object obj) 
    { 
     return true; 
    } 

    public override int GetHashCode() 
    { 
     return 0; 
    } 
} 

private static void doEqualityCompares() 
{ 
    var t1 = new Person(); 

    var hst = new HashSet<Person>(); 
    var hsi = new HashSet<IPerson>(); 

    hst.Add(t1); 
    hsi.Add(t1); 

    //Direct comparison 
    t1.Equals(t1);     //IEquatable<T>.Equals(T) 

    hst.Contains(t1);    //Equals(object) --> why? both sides inherit of IPerson... 
    hst.Contains((IPerson)t1);  //IEquatable<T>.Equals(T) 

    hsi.Contains(t1);    //IEquatable<T>.Equals(T) 
    hsi.Contains((IPerson)t1);  //IEquatable<T>.Equals(T) 
} 
+1

sollte nicht 'hst.Contains ((IPerson) t1);' nicht kompilieren? –

+0

@Dennis_E Siehe die zweite Hälfte meiner Antwort. – Servy

+1

@Dennis_E: Das ist nicht 'HashSet .Contains' aber' Enumerable.Contains', die die Schnittstelle akzeptiert. Dies ist also die langsame Version, die alle Elemente aufzählt und sie mit 'Equals (IPerson sony) 'vergleicht, anstatt' GetHashCode' zu ​​verwenden. –

Antwort

21

HashSet<T> Anrufe EqualityComparer<T>.Default den Standardgleichheitsvergleich erhalten, wenn kein Vergleich zur Verfügung gestellt.

EqualityComparer<T>.Default bestimmt, ob T implementiert IEquatable<T>. Wenn es das tut, verwendet es das, wenn nicht, verwendet es object.Equals und object.GetHashCode.

Ihr Person Objekt implementiert IEquatable<IPerson> nicht IEquatable<Person>.

Wenn Sie eine HashSet<Person> haben es am Ende überprüft, ob Person ein IEquatable<Person> ist, was es nicht, so verwendet sie die object Methoden.

Wenn Sie eine HashSet<IPerson> haben, überprüft es, ob IPerson eine IEquatable<IPerson> ist, was es ist, so dass es diese Methoden verwendet.


Was den verbleibenden Fall, warum die Linie tut:

hst.Contains((IPerson)t1); 

Aufruf der IEquatableEquals Methode, obwohl seine auf dem HashSet<Person> genannt. Hier rufen Sie Contains auf einem HashSet<Person> und übergeben in IPerson. HashSet<Person>.Contains erfordert, dass der Parameter ein Person ist; Ein IPerson ist kein gültiges Argument. Ein HashSet<Person> ist jedoch auch ein IEnumerable<Person>, und da IEnumerable<T> kovariant ist, bedeutet dies, dass es wie ein IEnumerable<IPerson> behandelt werden kann, das eine Contains Erweiterungsmethode (über LINQ) hat, die einen IPerson als Parameter akzeptiert.

IEnumerable.Contains verwendet auch EqualityComparer<T>.Default, um seinen Gleichheitsvergleich zu erhalten, wenn keiner bereitgestellt wird. Im Fall dieses Methodenaufrufs rufen wir tatsächlich Contains auf einer IEnumerable<IPerson> an, was bedeutet, EqualityComparer<IPerson>.Default überprüft, ob IPerson ein IEquatable<IPerson> ist, was es ist, so dass Equals Methode aufgerufen wird.

+0

Ich sehe - so die 'EqualityComparer .Default' kann eigentlich nur die' Equals (T) 'Methode, wenn es genau vom Typ T - es wäre nicht in der Lage, es von einem Typ T ', die geerbt von erhalten T. Ich dachte, es gäbe Polymorphismus im Spiel, aber offensichtlich lag ich völlig falsch. Danke für die Klarstellung. – Marwie

+0

Es sollte angemerkt werden, dass sie * nicht * gewählt haben, um 'IEquatable 'kontravariant in seinem Typ Argument' T' zu machen. Wenn sie 'IEquatable <>' contravariant gemacht hätten (wäre 'IEquatable ' gewesen), wäre etwas, das ein 'IEquatable ' war, implizit auch ein 'IEquatable ' gewesen. Allerdings würde ich sagen, dass die Kontravariante zu noch schlimmeren Problemen geführt hat (als das, was wir bereits haben), wenn Leute von Klassen abstammen, die 'IEquatable <>' implementieren. *** Edit: *** Ah, ich sehe supercat das schon in der anderen Antwort erwähnt. –

2

Obwohl IComparable<in T> ist kontra bezüglich T, so dass jede Art, die IComparable<Person> implementiert würde automatisch eine Implementierung IComparable<IPerson> betrachtet werden, die Art IEquatable<T> für die Verwendung mit abgedichteten Typen bestimmt ist, vor allem Strukturen. Die Anforderung, dass Object.GetHashCode() sowohl mit IEquatable<T>.Equals(T) als auch mit Object.Equals(Object) konsistent ist, impliziert im Allgemeinen, dass sich die beiden letztgenannten Verfahren identisch verhalten sollen, was wiederum impliziert, dass eine von ihnen sich an die andere ketten sollte. Es gibt zwar einen großen Leistungsunterschied zwischen der direkten Übergabe einer Struktur an eine IEquatable<T> Implementierung des richtigen Typs, verglichen mit dem Konstruieren einer Instanz des Boxed-Heap-Objekttyps der Struktur und mit einer Equals(Object) Implementierung, um die Strukturdaten daraus zu kopieren Performance-Unterschiede existieren mit Referenztypen. Wenn IEquatable<T>.Equals(T) und Equals(Object) werden gleichwertig und T ist eine vererbbare Referenz-Typ, gibt es keinen sinnvollen Unterschied zwischen:

bool Equals(MyType obj) 
{ 
    MyType other = obj as MyType; 
    if (other==null || other.GetType() != typeof(this)) 
    return false; 
    ... test whether other matches this 
} 

bool Equals(MyType other) 
{ 
    if (other==null || other.GetType() != typeof(this)) 
    return false; 
    ... test whether other matches this 
} 

Letztere Typumwandlung retten könnte, aber das ist unwahrscheinlich, dass eine ausreichende Performance-Unterschied machen zwei rechtfertigen zu müssen Methoden.

Verwandte Themen