Qual é a diferença entre IEqualityComparer <T> e IEquatable <T>?

151

Quero entender os cenários onde IEqualityComparer<T>e IEquatable<T>deve ser usado. A documentação do MSDN para os dois parece muito semelhante.

Tilak
fonte
1
MSDN sez: "Essa interface permite a implementação de comparação de igualdade personalizada para coleções ", o que obviamente é inferido na resposta selecionada. O MSDN também recomenda herdar em EqualityComparer<T>vez de implementar a interface "porque EqualityComparer<T>testa a igualdade usandoIEquatable<T>
radarbob 20/09/16
... o acima sugere que eu deveria criar uma coleção personalizada para qualquer Timplementação IEquatable<T>. List<T>Caso contrário, uma coleção teria algum tipo de bug sutil?
Radarbob 20/09/16
boa explicação anotherchris.net/csharp/...
Ramil Shavaleev
@RamilShavaleev o link está quebrado
ChessMax

Respostas:

117

IEqualityComparer<T>é uma interface para um objecto que executa a comparação de dois objectos do tipo T.

IEquatable<T>é para um objeto do tipo Tpara que ele possa se comparar com outro do mesmo tipo.

Justin Niessner
fonte
1
A mesma diferença está entre IComparable / IComparer
boctulus
3
Capitão Óbvio
Stepan Ivanenko
(Editado) IEqualityComparer<T>é uma interface para um objeto (que geralmente é uma classe leve diferente de T) que fornece funções de comparação que operamT
rwong 22/02
61

Ao decidir se deve usar IEquatable<T>ou IEqualityComparer<T>, pode-se perguntar:

Existe uma maneira preferida de testar duas instâncias de Tigualdade ou existem várias maneiras igualmente válidas?

  • Se houver apenas uma maneira de testar duas instâncias de Tigualdade, ou se um dos vários métodos for preferido, entãoIEquatable<T> seria a escolha certa: Essa interface deve ser implementada apenas por Tsi só, de modo que uma instância Ttenha conhecimento interno de como se comparar a outra instância de T.

  • Por outro lado, se houver vários métodos igualmente razoáveis ​​de comparar dois Ts para igualdade, IEqualityComparer<T>pareceria mais apropriado: Essa interface não deve ser implementada por Tela mesma, mas por outras classes "externas". Portanto, ao testar duas instâncias de Tigualdade, por Tnão ter um entendimento interno da igualdade, você terá que fazer uma escolha explícita de uma IEqualityComparer<T>instância que executa o teste de acordo com seus requisitos específicos.

Exemplo:

Vamos considerar esses dois tipos (que deveriam ter semântica de valor ):

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

Por que apenas um desses tipos herdaria IEquatable<>, mas não o outro?

Em teoria, existe apenas uma maneira sensata de comparar duas instâncias de qualquer tipo: elas são iguais se a XeY propriedades em ambas as instâncias forem iguais. De acordo com esse pensamento, os dois tipos devem implementar IEquatable<>, porque não parece provável que haja outras maneiras significativas de se fazer um teste de igualdade.

O problema aqui é que comparar números de ponto flutuante por igualdade pode não funcionar como esperado, devido a erros de arredondamento de minutos . Existem métodos diferentes de comparar números de ponto flutuante para quase-igualdade , cada um com vantagens e vantagens específicas, e você pode querer escolher qual método é apropriado.

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

Observe que a página à qual vinculei (acima) declara explicitamente que esse teste de quase igualdade tem alguns pontos fracos. Como essa é uma IEqualityComparer<T>implementação, você pode simplesmente trocá-la se não for boa o suficiente para seus propósitos.

stakx - não está mais contribuindo
fonte
6
O método de comparação sugerido para testar a igualdade de ponto duplo está quebrado, já que qualquer IEqualityComparer<T>implementação legítima deve implementar uma relação de equivalência, o que implica que em todos os casos em que dois objetos se comparam a um terço, eles devem ser comparados. Qualquer classe que implemente IEquatable<T>ou IEqualityComparer<T>de uma maneira contrária ao acima é quebrada.
Supercat 19/03
5
Um exemplo melhor seria comparações entre cadeias. Faz sentido ter um método de comparação de strings que considera as strings iguais apenas se elas contiverem a mesma sequência de bytes, mas existem outros métodos de comparação úteis que também constituem relações de equivalência , como comparações sem distinção entre maiúsculas e minúsculas. Observe que um IEqualityComparer<String>que considera "olá" igual a "Olá" e "hElLo" deve considerar "Olá" e "hElLo" iguais entre si, mas para a maioria dos métodos de comparação isso não seria um problema.
Supercat 19/03
@ supercat, obrigado pelo feedback valioso. É provável que eu não consiga revisar minha resposta nos próximos dias. Portanto, se você quiser, sinta-se à vontade para editar como achar melhor.
stakx - não está mais contribuindo em 20/03/13
Isso é exatamente o que eu precisava e o que estou fazendo. No entanto, é necessário substituir GetHashCode, no qual ele retornará false quando os valores forem diferentes. Como você lida com seu GetHashCode?
teapeng
@ teapeng: Não tenho certeza de qual caso de uso específico você está falando. De um modo geral, GetHashCodevocê deve determinar rapidamente se dois valores diferem. As regras são basicamente as seguintes: (1) GetHashCode sempre deve produzir o mesmo código de hash para o mesmo valor. (2) GetHashCode deve ser rápido (mais rápido que Equals). (3) GetHashCode não precisa ser preciso (não tão preciso quanto Equals). Isso significa que ele pode produzir o mesmo código de hash para valores diferentes. Quanto mais preciso você conseguir, melhor, mas provavelmente é mais importante mantê-lo rápido.
stakx - não está mais contribuindo com
27

Você já tem a definição básica do que são . Em resumo, se você implementar IEquatable<T>na classe T, o Equalsmétodo em um objeto do tipo Tinformará se o próprio objeto (aquele que está sendo testado quanto à igualdade) é igual a outra instância do mesmo tipo T. Visto que, IEqualityComparer<T>é para testar a igualdade de quaisquer duas instâncias de T, normalmente fora do escopo das instâncias de T.

Quanto ao que eles servem pode ser confuso no começo. A partir da definição, deve ficar claro que, portanto IEquatable<T>(definido na Tprópria classe ) deve ser o padrão de fato para representar a exclusividade de seus objetos / instâncias. HashSet<T>, Dictionary<T, U>(Considerando GetHashCodeé substituído tão bem), Containssobre List<T>etc fazer uso deste. Implementação IEqualityComparer<T>em Tnão ajuda os casos gerais acima mencionados. Posteriormente, há pouco valor para implementar IEquatable<T>em qualquer outra classe que não seja T. Este:

class MyClass : IEquatable<T>

raramente faz sentido.

Por outro lado

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

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

é assim que deve ser feito.

IEqualityComparer<T>pode ser útil quando você precisar de uma validação personalizada da igualdade, mas não como regra geral. Por exemplo, em uma classe de Personem algum momento você pode precisar testar a igualdade de duas pessoas com base na idade delas. Nesse caso, você pode fazer:

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

Para testá-los, tente

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

Da mesma forma IEqualityComparer<T>em Tnão faz sentido.

class Person : IEqualityComparer<Person>

É verdade que isso funciona, mas não parece bom para os olhos e derrota a lógica.

Geralmente o que você precisa é IEquatable<T>. Idealmente, você pode ter apenas um, IEquatable<T>enquanto múltiplos IEqualityComparer<T>são possíveis com base em critérios diferentes.

O IEqualityComparer<T>e IEquatable<T>são exatamente análogos Comparer<T>e IComparable<T>que são usados ​​para fins de comparação ao invés de equacionar; um bom tópico aqui onde escrevi a mesma resposta :)

nawfal
fonte
public int GetHashCode(Person obj)deve retornarobj.GetHashCode()
hyankov 02/12/19
@HristoYankov deve retornar obj.Age.GetHashCode. irá editar.
Nawfal 2/16
Por favor, explique por que o comparador deve fazer uma suposição sobre o que o Hash do objeto compara? Seu comparador não sabe como o Person calcula seu Hash, por que você o substituiu?
hyankov 02/12/19
@HristoYankov Seu comparador não sabe como o Person calcula seu Hash - Claro, é por isso que não ligamos diretamente para person.GetHashCodelugar nenhum .... por que você o substituiu? - Substituímos porque o objetivo principal IEqualityCompareré ter uma implementação de comparação diferente de acordo com nossas regras - as regras que realmente conhecemos bem.
Nawfal 3/16
No meu exemplo, preciso basear minha comparação na Agepropriedade, então ligo Age.GetHashCode. A idade é do tipo int, mas qualquer que seja o tipo de contrato de código hash no .NET é esse different objects can have equal hash but equal objects cant ever have different hashes. Este é um contrato em que podemos acreditar cegamente. Certamente, nossos comparadores personalizados também devem seguir essa regra. Se alguma vez a chamada someObject.GetHashCodequebra as coisas, então é o problema com os implementadores do tipo de someObject, não é o nosso problema.
Nawfal 3/16
11

IEqualityComparer é para uso quando a igualdade de dois objetos é implementada externamente, por exemplo, se você deseja definir um comparador para dois tipos para os quais você não tem a fonte, ou para casos em que a igualdade entre duas coisas só faz sentido em algum contexto limitado.

IEquatable é para o próprio objeto (aquele que está sendo comparado em termos de igualdade) para implementar.

Chris Shain
fonte
Você é o único que realmente levantou a questão "Conectando a igualdade" (uso externo). mais 1.
Royi Namir
4

Um compara dois Ts. O outro pode se comparar a outros Ts. Normalmente, você só precisará usar um de cada vez, não os dois.

Matt Ball
fonte