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.
T
implementação IEquatable<T>
. List<T>
Caso contrário, uma coleção teria algum tipo de bug sutil?
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.
T
implementação IEquatable<T>
. List<T>
Caso contrário, uma coleção teria algum tipo de bug sutil?
Respostas:
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 T
para que ele possa se comparar com outro do mesmo tipo.
IEqualityComparer<T>
é uma interface para um objeto (que geralmente é uma classe leve diferente de T
) que fornece funções de comparação que operamT
Ao decidir se deve usar IEquatable<T>
ou IEqualityComparer<T>
, pode-se perguntar:
Existe uma maneira preferida de testar duas instâncias de
T
igualdade ou existem várias maneiras igualmente válidas?
Se houver apenas uma maneira de testar duas instâncias de T
igualdade, ou se um dos vários métodos for preferido, entãoIEquatable<T>
seria a escolha certa: Essa interface deve ser implementada apenas por T
si só, de modo que uma instância T
tenha 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 T
s para igualdade, IEqualityComparer<T>
pareceria mais apropriado: Essa interface não deve ser implementada por T
ela mesma, mas por outras classes "externas". Portanto, ao testar duas instâncias de T
igualdade, por T
nã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 X
eY
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.
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.
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.
GetHashCode
você 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.
Você já tem a definição básica do que são . Em resumo, se você implementar IEquatable<T>
na classe T
, o Equals
método em um objeto do tipo T
informará 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 T
pró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), Contains
sobre List<T>
etc fazer uso deste. Implementação IEqualityComparer<T>
em T
nã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 Person
em 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 T
nã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 :)
public int GetHashCode(Person obj)
deve retornarobj.GetHashCode()
obj.Age.GetHashCode
. irá editar.
person.GetHashCode
lugar 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.
Age
propriedade, 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.GetHashCode
quebra as coisas, então é o problema com os implementadores do tipo de someObject
, não é o nosso problema.
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.
EqualityComparer<T>
vez de implementar a interface "porqueEqualityComparer<T>
testa a igualdade usandoIEquatable<T>