Quelle est la différence entre IEqualityComparer <T> et IEquatable <T>?

151

Je veux comprendre les scénarios où IEqualityComparer<T>et IEquatable<T>doivent être utilisés. La documentation MSDN pour les deux semble très similaire.

Tilak
la source
1
MSDN sez: "Cette interface permet la mise en œuvre d'une comparaison d'égalité personnalisée pour les collections. " Qui est bien sûr déduite dans la réponse sélectionnée. MSDN recommande également d'hériter EqualityComparer<T>au lieu d'implémenter l'interface "car EqualityComparer<T>teste l'égalité à l'aide deIEquatable<T>
radarbob
... ce qui précède suggère que je devrais créer une collection personnalisée pour toute Timplémentation IEquatable<T>. Une collection List<T>aurait-elle une sorte de bogue subtil autrement?
radarbob
bonne explication anotherchris.net/csharp/…
Ramil Shavaleev
@RamilShavaleev le lien est rompu
ChessMax

Réponses:

117

IEqualityComparer<T>est une interface pour un objet qui effectue la comparaison sur deux objets du type T.

IEquatable<T>est pour un objet de type Tafin qu'il puisse se comparer à un autre du même type.

Justin Niessner
la source
1
La même différence est entre IComparable / IComparer
boctulus
3
Captain Obvious
Stepan Ivanenko
(Modifié) IEqualityComparer<T>est une interface pour un objet (qui est généralement une classe légère différente de T) qui fournit des fonctions de comparaison qui fonctionnent surT
rwong
62

Au moment de décider d'utiliser IEquatable<T>ou IEqualityComparer<T>, on pourrait se demander:

Existe-t-il un moyen préféré de tester deux instances de Tpour l'égalité, ou existe-t-il plusieurs moyens également valides?

  • S'il n'y a qu'une seule façon de tester deux instances de Tpour l'égalité, ou si l'une des méthodes est préférée, alors ce IEquatable<T>serait le bon choix: cette interface est censée être implémentée uniquement par Telle-même, de sorte qu'une instance de Ta une connaissance interne de comment se comparer à une autre instance de T.

  • D'un autre côté, s'il existe plusieurs méthodes également raisonnables pour comparer deux Ts pour l'égalité, IEqualityComparer<T>cela semblerait plus approprié: Cette interface n'est pas destinée à être implémentée par Telle-même, mais par d'autres classes «externes». Par conséquent, lorsque vous testez deux instances de Tpour l'égalité, parce que Tvous n'avez aucune compréhension interne de l'égalité, vous devrez faire un choix explicite d'une IEqualityComparer<T>instance qui effectue le test en fonction de vos besoins spécifiques.

Exemple:

Considérons ces deux types (qui sont supposés avoir une sémantique de valeur ):

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

Pourquoi un seul de ces types hériterait-il IEquatable<>, mais pas l'autre?

En théorie, il n'existe qu'une seule manière raisonnable de comparer deux instances de l'un ou l'autre type: elles sont égales si les propriétés Xet Ydans les deux instances sont égales. Selon cette réflexion, les deux types devraient être implémentés IEquatable<>, car il ne semble pas probable qu'il existe d'autres moyens significatifs de faire un test d'égalité.

Le problème ici est que la comparaison des nombres à virgule flottante pour l'égalité peut ne pas fonctionner comme prévu, en raison d'erreurs d'arrondi infimes . Il existe différentes méthodes de comparaison des nombres à virgule flottante pour la quasi-égalité , chacune avec des avantages et des compromis spécifiques, et vous voudrez peut-être être en mesure de choisir vous-même la méthode appropriée.

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

Notez que la page à laquelle j'ai lié (ci-dessus) indique explicitement que ce test de quasi-égalité présente quelques faiblesses. Puisqu'il s'agit d'une IEqualityComparer<T>implémentation, vous pouvez simplement l'échanger si ce n'est pas assez bon pour vos besoins.

stakx - ne contribue plus
la source
6
La méthode de comparaison suggérée pour tester l'égalité à deux points est rompue, car toute IEqualityComparer<T>implémentation légitime doit implémenter une relation d'équivalence, ce qui implique que dans tous les cas où deux objets se comparent égaux à un tiers, ils doivent se comparer égaux l'un à l'autre. Toute classe qui implémente IEquatable<T>ou IEqualityComparer<T>d'une manière contraire à ce qui précède est rompue.
supercat
5
Un meilleur exemple serait les comparaisons entre les chaînes. Il est logique d'avoir une méthode de comparaison de chaînes qui considère les chaînes comme égales uniquement si elles contiennent la même séquence d'octets, mais il existe d'autres méthodes de comparaison utiles qui constituent également des relations d'équivalence , telles que des comparaisons insensibles à la casse. Notez qu'un IEqualityComparer<String>qui considère "bonjour" comme égal à la fois "Hello" et "hElLo" doit considérer "Hello" et "hElLo" comme étant égaux l'un à l'autre, mais pour la plupart des méthodes de comparaison, ce ne serait pas un problème.
supercat
@supercat, merci pour vos précieux commentaires. Il est probable que je ne reviendrai pas sur ma réponse dans les prochains jours, donc si vous le souhaitez, n'hésitez pas à modifier comme bon vous semble.
stakx - ne contribue plus
C'est exactement ce dont j'avais besoin et ce que je fais. Cependant, il est nécessaire de remplacer GetHashCode, dans lequel il retournera false une fois que les valeurs seront différentes. Comment gérez-vous votre GetHashCode?
teapeng
@teapeng: Je ne sais pas de quel cas d'utilisation spécifique vous parlez. D'une manière générale, cela GetHashCodedevrait vous permettre de déterminer rapidement si deux valeurs diffèrent. Les règles sont à peu près les suivantes: (1) GetHashCode doit toujours produire le même code de hachage pour la même valeur. (2) GetHashCode doit être rapide (plus rapide que Equals). (3) GetHashCode n'a pas besoin d'être précis (pas aussi précis que Equals). Cela signifie qu'il peut produire le même code de hachage pour différentes valeurs. Plus vous pouvez le rendre précis, mieux c'est, mais il est probablement plus important de le garder rapide.
stakx - ne contribue plus
27

Vous avez déjà la définition de base de ce qu'ils sont . En bref, si vous implémentez IEquatable<T>sur classe T, la Equalsméthode sur un objet de type Tvous indique si l'objet lui-même (celui qui est testé pour l'égalité) est égal à une autre instance du même type T. Considérant que, IEqualityComparer<T>sert à tester l'égalité de deux instances de T, généralement en dehors de la portée des instances de T.

Quant à savoir à quoi ils servent peut être déroutant au début. D'après la définition, il devrait être clair que par conséquent IEquatable<T>(défini dans la classe Telle - même) devrait être la norme de facto pour représenter l'unicité de ses objets / instances. HashSet<T>, Dictionary<T, U>(la considération GetHashCodeest également remplacée), Containssur List<T>etc. La mise IEqualityComparer<T>en œuvre sur Tn'aide pas les cas généraux mentionnés ci-dessus. Par la suite, il y a peu de valeur pour l'implémentation IEquatable<T>sur une autre classe que T. Ce:

class MyClass : IEquatable<T>

fait rarement sens.

D'autre part

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

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

c'est comment cela devrait être fait.

IEqualityComparer<T>peut être utile lorsque vous avez besoin d'une validation personnalisée de l'égalité, mais pas en règle générale. Par exemple, dans une classe de Personà un moment donné, vous devrez peut-être tester l'égalité de deux personnes en fonction de leur âge. Dans ce cas, vous pouvez faire:

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

Pour les tester, essayez

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

De même IEqualityComparer<T>, cela Tn'a pas de sens.

class Person : IEqualityComparer<Person>

C'est vrai que cela fonctionne, mais cela ne semble pas bon aux yeux et va à l'encontre de la logique.

Ce dont vous avez généralement besoin, c'est IEquatable<T>. Idéalement aussi, vous ne pouvez en avoir qu'un IEquatable<T>alors que plusieurs IEqualityComparer<T>sont possibles en fonction de différents critères.

Les IEqualityComparer<T>et IEquatable<T>sont exactement analogues à Comparer<T>et IComparable<T>qui sont utilisés à des fins de comparaison plutôt que d'assimilation; un bon fil ici où j'ai écrit la même réponse :)

nawfal
la source
public int GetHashCode(Person obj)devrait revenirobj.GetHashCode()
hyankov
@HristoYankov devrait revenir obj.Age.GetHashCode. va éditer.
nawfal
Veuillez expliquer pourquoi le comparateur doit faire une telle hypothèse sur le hachage de l'objet qu'il compare? Votre comparateur ne sait pas comment Person calcule son hachage, pourquoi le remplaceriez-vous?
hyankov
@HristoYankov Votre comparateur ne sait pas comment Person calcule son Hash - Bien sûr, c'est pourquoi nous n'avons appelé directement person.GetHashCodenulle part .... pourquoi voudriez-vous remplacer cela? - Nous remplaçons parce que tout l'intérêt IEqualityComparerest d'avoir une implémentation de comparaison différente selon nos règles - les règles que nous connaissons vraiment bien.
nawfal
Dans mon exemple, je dois baser ma comparaison sur la Agepropriété, donc j'appelle Age.GetHashCode. L'âge est de type int, mais quel que soit le type, c'est le contrat de code de hachage dans .NET different objects can have equal hash but equal objects cant ever have different hashes. C'est un contrat auquel nous pouvons croire aveuglément. Bien sûr, nos comparateurs personnalisés doivent également adhérer à cette règle. Si jamais appeler someObject.GetHashCodecasse les choses, alors c'est le problème avec les implémenteurs de type de someObject, ce n'est pas notre problème.
nawfal
11

IEqualityComparer est à utiliser lorsque l'égalité de deux objets est implémentée en externe, par exemple si vous souhaitez définir un comparateur pour deux types dont vous n'avez pas la source, ou pour les cas où l'égalité entre deux choses n'a de sens que dans un contexte limité.

IEquatable est pour l'objet lui-même (celui qui est comparé pour l'égalité) à implémenter.

Chris Shain
la source
Vous êtes le seul à avoir soulevé le problème du "Plugging in Equality" (usage externe). plus 1.
Royi Namir
4

On compare deux Tart. L'autre peut se comparer à d'autres Tart. Habituellement, vous n'aurez besoin d'en utiliser qu'un à la fois, pas les deux.

Matt Ball
la source