Qual è la differenza tra IEqualityComparer <T> e IEquatable <T>?


151

Voglio capire gli scenari dove IEqualityComparer<T>e IEquatable<T>dovrebbero essere usati. La documentazione MSDN per entrambi sembra molto simile.


1
MSDN sez: "Questa interfaccia consente l'implementazione di un confronto di uguaglianza personalizzato per le raccolte ", che ovviamente viene dedotto nella risposta selezionata. MSDN raccomanda inoltre di ereditare EqualityComparer<T>invece di implementare l'interfaccia "perché EqualityComparer<T>verifica l'uguaglianza usandoIEquatable<T>
radarbob il

... quanto sopra suggerisce che dovrei creare una raccolta personalizzata per qualsiasi Timplementazione IEquatable<T>. List<T>In caso contrario, una raccolta come quella avrebbe una specie di bug sottile?
radarbob,


@RamilShavaleev il collegamento è interrotto
ChessMax

Risposte:


117

IEqualityComparer<T>è un'interfaccia per un oggetto che esegue il confronto su due oggetti del tipo T.

IEquatable<T>è per un oggetto di tipo in Tmodo che possa confrontarsi con un altro dello stesso tipo.


1
La stessa differenza è tra IComparable / IComparer
boctulus,

3
Capitano Ovvio
Stepan Ivanenko,

(Modificato) IEqualityComparer<T>è un'interfaccia per un oggetto (che di solito è una classe leggera diversa da T) che fornisce funzioni di confronto che funzionano suT
rwong

62

Quando si decide se utilizzare IEquatable<T>o IEqualityComparer<T>, si potrebbe chiedere:

Esiste un modo preferito di testare due casi di Tuguaglianza o ci sono molti modi ugualmente validi?

  • Se esiste un solo modo per testare due istanze di Tper l'uguaglianza, o se si preferisce uno dei diversi metodi, allora IEquatable<T>sarebbe la scelta giusta: questa interfaccia dovrebbe essere implementata solo da Tsola, in modo che un'istanza Tabbia una conoscenza interna di come confrontarsi con un'altra istanza di T.

  • D'altra parte, se esistono diversi metodi ugualmente ragionevoli per confrontare due Ts per l'uguaglianza, IEqualityComparer<T>sembrerebbe più appropriato: questa interfaccia non è pensata per essere implementata da Tsola, ma da altre classi "esterne". Pertanto, quando si verificano due istanze Tper l'uguaglianza, poiché Tnon ha una comprensione interna dell'uguaglianza, sarà necessario fare una scelta esplicita di IEqualityComparer<T>un'istanza che esegue il test in base alle proprie esigenze specifiche.

Esempio:

Consideriamo questi due tipi (che dovrebbero avere una semantica di valore ):

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; }
}

Perché solo uno di questi tipi erediterebbe IEquatable<>, ma non l'altro?

In teoria, esiste un solo modo ragionevole per confrontare due istanze di entrambi i tipi: sono uguali se le proprietà Xe Yin entrambi i casi sono uguali. Secondo questo pensiero, entrambi i tipi dovrebbero implementarsi IEquatable<>, perché non sembra probabile che ci siano altri modi significativi di fare un test di uguaglianza.

Il problema qui è che il confronto di numeri in virgola mobile per l'uguaglianza potrebbe non funzionare come previsto, a causa di errori di arrotondamento minuti . Esistono diversi metodi per confrontare i numeri in virgola mobile per la quasi-uguaglianza , ciascuno con vantaggi e compromessi specifici e potresti voler scegliere tu stesso quale metodo è appropriato.

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;
    }
    
}

Si noti che la pagina a cui ho collegato (sopra) afferma esplicitamente che questo test per la quasi uguaglianza presenta alcuni punti deboli. Poiché questa è IEqualityComparer<T>un'implementazione, puoi semplicemente cambiarla se non è abbastanza buona per i tuoi scopi.


6
Il metodo di confronto suggerito per testare l'uguaglianza in doppio punto è rotto, poiché qualsiasi IEqualityComparer<T>implementazione legittima deve implementare una relazione di equivalenza, il che implica che in tutti i casi in cui due oggetti si confrontano uguali a un terzo, devono confrontarsi uguali tra loro. Ogni classe che implementa IEquatable<T>o IEqualityComparer<T>in modo contrario a quanto sopra è rotta.
supercat

5
Un esempio migliore sarebbe il confronto tra stringhe. Ha senso disporre di un metodo di confronto delle stringhe che considera le stringhe uguali solo se contengono la stessa sequenza di byte, ma esistono altri metodi di confronto utili che costituiscono anche relazioni di equivalenza , come i confronti senza distinzione tra maiuscole e minuscole. Si noti che uno IEqualityComparer<String>che considera "ciao" uguale sia a "Ciao" che a "hlEl" deve considerare "Ciao" e "hlEl" uguali tra loro, ma per la maggior parte dei metodi di confronto non sarebbe un problema.
supercat

@supercat, grazie per il prezioso feedback. È probabile che non andrò in giro a rivedere la mia risposta nei prossimi giorni, quindi se vuoi, sentiti libero di modificare come ritieni opportuno.
stakx - non contribuisce più al

Questo è esattamente ciò di cui avevo bisogno e cosa sto facendo. Tuttavia, è necessario sovrascrivere GetHashCode, in cui restituirà false una volta che i valori sono diversi. Come gestisci il tuo GetHashCode?
teapeng,

@teapeng: non sono sicuro di quale caso d'uso specifico stia parlando. In generale, GetHashCodedovrebbe consentire di determinare rapidamente se due valori differiscono. Le regole sono approssimativamente le seguenti: (1) GetHashCode deve sempre produrre lo stesso codice hash per lo stesso valore. (2) GetHashCode dovrebbe essere veloce (più veloce di Equals). (3) GetHashCode non deve essere preciso (non preciso come Equals). Ciò significa che può produrre lo stesso codice hash per valori diversi. Più preciso puoi farlo, meglio è, ma probabilmente è più importante mantenerlo veloce.
stakx - non contribuisce più il

27

Hai già ottenuto la definizione di base di ciò che sono . In breve, se si implementa IEquatable<T>in classe T, il Equalsmetodo su un oggetto di tipo Tindica se l'oggetto stesso (quello che viene testato per l'uguaglianza) è uguale a un'altra istanza dello stesso tipo T. Considerando che, IEqualityComparer<T>è per testare l'uguaglianza di due casi qualsiasi T, in genere al di fuori dell'ambito dei casi di T.

Quanto a cosa servono possono essere fonte di confusione all'inizio. Dalla definizione dovrebbe essere chiaro che quindi IEquatable<T>(definito nella classe Tstessa) dovrebbe essere lo standard di fatto per rappresentare l'unicità dei suoi oggetti / istanze. HashSet<T>, Dictionary<T, U>(Considerando GetHashCodeviene sovrascritto pure), Containsil List<T>etc fanno uso di questo. L'implementazione IEqualityComparer<T>su Tnon aiuta i casi generali sopra menzionati. Successivamente, c'è poco valore per l'implementazione IEquatable<T>su qualsiasi altra classe diversa da T. Questo:

class MyClass : IEquatable<T>

raramente ha senso.

D'altro canto

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

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

è come dovrebbe essere fatto.

IEqualityComparer<T>può essere utile quando si richiede una convalida personalizzata dell'uguaglianza, ma non come regola generale. Ad esempio, in una classe Personad un certo punto potrebbe essere necessario testare l'uguaglianza di due persone in base alla loro età. In tal caso puoi fare:

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;
    }
}

Per provarli, prova

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

Allo stesso modo IEqualityComparer<T>il Tnon ha senso.

class Person : IEqualityComparer<Person>

È vero che funziona, ma non ha un bell'aspetto per gli occhi e sconfigge la logica.

Di solito quello che ti serve è IEquatable<T>. Inoltre, idealmente, puoi averne solo uno IEquatable<T>mentre IEqualityComparer<T>sono possibili multipli in base a criteri diversi.

I IEqualityComparer<T>e IEquatable<T>sono esattamente analoghi a Comparer<T>e IComparable<T>che sono usati a fini di confronto piuttosto che equivalenti; un buon thread qui dove ho scritto la stessa risposta :)


public int GetHashCode(Person obj)dovrebbe tornareobj.GetHashCode()
hyankov il

@HristoYankov dovrebbe tornare obj.Age.GetHashCode. modificherà.
nawfal,

Spiega perché il comparatore deve fare una simile ipotesi su ciò che l'Hash dell'oggetto confronta? Il tuo comparatore non sa come la Persona calcola il suo hash, perché dovresti ignorarlo?
Hyankov,

@HristoYankov Il tuo comparatore non sa come la Persona calcola il suo Hash - Certo, ecco perché non abbiamo chiamato direttamente da person.GetHashCodenessuna parte .... perché dovresti ignorarlo? - Ignoriamo perché il punto centrale IEqualityComparerè avere una diversa implementazione del confronto secondo le nostre regole - le regole che conosciamo davvero bene.
nawfal,

Nel mio esempio, ho bisogno di basare il mio confronto sulla Ageproprietà, quindi chiamo Age.GetHashCode. Age è di tipo int, ma qualunque sia il tipo è il contratto di codice hash in .NET è quello different objects can have equal hash but equal objects cant ever have different hashes. Questo è un contratto a cui possiamo credere ciecamente. Sicuramente anche i nostri comparatori personalizzati dovrebbero aderire a questa regola. Se mai chiamare someObject.GetHashCodeinterrompe le cose, allora è il problema con gli implementatori di tipo di someObject, non è il nostro problema.
nawfal,

11

IEqualityComparer deve essere utilizzato quando l'uguaglianza di due oggetti viene implementata esternamente, ad esempio se si desidera definire un comparatore per due tipi per i quali non si ha la fonte o per i casi in cui l'uguaglianza tra due cose ha senso solo in un contesto limitato.

IEquatable è da implementare per l'oggetto stesso (quello che viene confrontato per l'uguaglianza).


Sei l'unico che ha effettivamente sollevato il problema "Plugging in Equality" (utilizzo esterno). più 1.
Royi Namir,

4

Uno confronta due Ts. L'altro può confrontarsi con gli altri T. Di solito, dovrai usarne solo uno alla volta, non entrambi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.