2015-02-10 11 views
20

Sono consapevole del fatto che devo sempre eseguire l'override di Equals(object) e GetHashCode() quando si implementa IEquatable<T>.Equals(T).Perché Equals (oggetto) vince su Equals (T) quando si utilizza un oggetto ereditato in Hashset o altre raccolte?

Tuttavia, non capisco, perché in alcune situazioni lo Equals(object) vince sul generico Equals(T).

Ad esempio, perché succede? Se dichiaro IEquatable<T> per un'interfaccia e implementiamo un tipo concreto X per questo, il generale Equals(object) viene chiamato da un Hashset<X> quando si confrontano gli elementi di quel tipo l'uno contro l'altro. In tutte le altre situazioni in cui almeno uno dei lati viene trasmesso all'interfaccia, viene chiamato il Equals(T) corretto.

Ecco un esempio di codice per dimostrare:

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

non dovrebbe 'hst.Contains ((IPerson) t1);' impossibile compilare? –

+0

@Dennis_E Guarda la seconda metà della mia risposta. – Servy

+1

@Dennis_E: non è 'HashSet .Contains' ma' Enumerable.Contains' che accetta l'interfaccia. Quindi questa è la versione lenta che enumera tutti gli elementi e li confronta con 'Equals (IPerson altro)' invece di usare 'GetHashCode'. –

risposta

21

HashSet<T> chiamate EqualityComparer<T>.Default per ottenere il confronto uguaglianze predefinito quando non viene fornita alcuna di confronto.

EqualityComparer<T>.Default determina se T implementa IEquatable<T>. Se lo fa, lo utilizza, in caso contrario, utilizza object.Equals e object.GetHashCode.

I vostri oggetti PersonIEquatable<IPerson> non IEquatable<Person>.

Quando hai un HashSet<Person> finisce per verificare se Person è un IEquatable<Person>, che non la sua, in modo che utilizza i metodi object.

Quando si dispone di un HashSet<IPerson> si verifica se IPerson è un IEquatable<IPerson>, che è, quindi utilizza tali metodi.


Come per il caso rimanente, perché fa la linea:

hst.Contains((IPerson)t1); 

chiamata IEquatableEquals metodo anche se la sua chiamata sul HashSet<Person>. Qui stai chiamando Contains su un HashSet<Person> e passando in un IPerson. HashSet<Person>.Contains richiede che il parametro sia Person; un IPerson non è un argomento valido. Tuttavia, un HashSet<Person> è anche un IEnumerable<Person> e poiché IEnumerable<T> è covariante, ciò significa che può essere trattato come un IEnumerable<IPerson>, che ha un metodo di estensione Contains (tramite LINQ) che accetta come parametro un IPerson.

IEnumerable.Contains utilizza anche EqualityComparer<T>.Default per ottenere il suo confronto di uguaglianza quando non viene fornito. Nel caso di questa chiamata di metodo stiamo in realtà chiamando Contains su un IEnumerable<IPerson>, il che significa che EqualityComparer<IPerson>.Default sta verificando se IPerson è un IEquatable<IPerson>, che è, in modo che venga chiamato il metodo Equals.

+0

Vedo, quindi "EqualityComparer .Default" può effettivamente ottenere il metodo 'Equals (T)' se è esattamente del tipo T - non sarebbe in grado di ottenerlo di un tipo T 'che ereditato da T. Pensavo che ci fosse del polimorfismo nel gioco ma ovviamente mi sbagliavo completamente. Grazie per il chiarimento. – Marwie

+0

Va notato che hanno scelto * non * per rendere 'IEquatable ' controvariante nel suo argomento tipo 'T'. Se avessero reso 'IEquatable <>' controvariante (sarebbe stato 'IEquatable '), qualcosa che era un 'IEquatable ' sarebbe stato implicitamente un 'IEquatable '. Tuttavia, immagino che renderlo controverso avrebbe portato a problemi ancora peggiori (rispetto a quello che abbiamo già) quando le persone derivano da classi che implementano 'IEquatable <>'. *** Modifica: *** Ah, vedo la supercat già menzionata nell'altra risposta. –

2

Sebbene IComparable<in T> è controvariante rispetto a T, in modo che qualsiasi tipo che implementa IComparable<Person> sarebbe automaticamente considerato un'implementazione IComparable<IPerson>, tipo IEquatable<T> è destinato all'uso con tipi sigillati, specialmente strutture. Il requisito che Object.GetHashCode() sia coerente con entrambi IEquatable<T>.Equals(T) e Object.Equals(Object) implica in genere che gli ultimi due metodi si comportino in modo identico, il che a sua volta implica che uno di essi debba concatenarsi all'altro. Mentre c'è una grande differenza di prestazioni tra il passaggio di una struct direttamente a un'implementazione IEquatable<T> del tipo corretto, confrontato con la costruzione di un'istanza del tipo boxed-heap-object della struttura e con un'implementazione Equals(Object), copiare i dati della struttura da quello, non le prestazioni diverse esistono con i tipi di riferimento. Se IEquatable<T>.Equals(T) e Equals(Object) stanno per essere equivalenti e T è un tipo di riferimento ereditabile, non c'è alcuna differenza significativa tra:

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 
} 

Quest'ultimo potrebbe risparmiare un typecast, ma questo è improbabile che una differenza di prestazioni sufficienti a giustificare il due metodi.

Problemi correlati