¿Cuál es la diferencia entre IEqualityComparer <T> e IEquatable <T>?

151

Quiero entender los escenarios donde IEqualityComparer<T>y IEquatable<T>deberían usarse. La documentación de MSDN para ambos se ve muy similar.

Tilak
fuente
1
MSDN sez: "Esta interfaz permite la implementación de una comparación de igualdad personalizada para colecciones ", que por supuesto se infiere en la respuesta seleccionada. MSDN también recomienda heredar en EqualityComparer<T>lugar de implementar la interfaz "porque EqualityComparer<T>prueba la igualdad usandoIEquatable<T>
radarbob
... lo anterior sugiere que debería crear una colección personalizada para cualquier Timplementación IEquatable<T>. ¿De List<T>lo contrario, una colección tiene algún tipo de error sutil?
radarbob
buena explicación anotherchris.net/csharp/…
Ramil Shavaleev
@RamilShavaleev el enlace está roto
ChessMax

Respuestas:

117

IEqualityComparer<T>es una interfaz para un objeto que realiza la comparación en dos objetos del tipo T.

IEquatable<T>es para un objeto de tipo Tpara que pueda compararse con otro del mismo tipo.

Justin Niessner
fuente
1
La misma diferencia es entre IComparable / IComparer
boctulus
3
Capitán Obvio
Stepan Ivanenko
(Edited) IEqualityComparer<T>es una interfaz para un objeto (que normalmente es una diferente clase de peso ligero de T) que proporciona funciones de comparación que opera enT
rwong
61

Al decidir si usar IEquatable<T>o IEqualityComparer<T>, uno podría preguntar:

¿Existe una forma preferida de probar dos instancias de Tigualdad, o hay varias formas igualmente válidas?

  • Si solo hay una forma de probar dos instancias de Tigualdad, o si se prefiere uno de varios métodos, entonces IEquatable<T>sería la elección correcta: se supone que esta interfaz se implementa solo por Tsí misma, de modo que una instancia de Ttiene conocimiento interno de cómo compararse con otra instancia de T.

  • Por otro lado, si hay varios métodos igualmente razonables de comparar dos Ts para la igualdad, IEqualityComparer<T>parecería más apropiado: esta interfaz no debe implementarse por Tsí misma, sino por otras clases "externas". Por lo tanto, al probar dos instancias de Tigualdad, ya que Tno tiene una comprensión interna de la igualdad, tendrá que hacer una elección explícita de una IEqualityComparer<T>instancia que realice la prueba de acuerdo con sus requisitos específicos.

Ejemplo:

Consideremos estos dos tipos (que se supone que tienen una 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 qué solo uno de estos tipos heredaría IEquatable<>, pero no el otro?

En teoría, solo hay una manera sensata de comparar dos instancias de cualquier tipo: son iguales si las propiedades Xy Yen ambas instancias son iguales. De acuerdo con este pensamiento, ambos tipos deberían implementarse IEquatable<>, porque no parece probable que haya otras formas significativas de hacer una prueba de igualdad.

El problema aquí es que comparar números de coma flotante para la igualdad podría no funcionar como se esperaba, debido a errores de redondeo minucioso . Existen diferentes métodos para comparar números de punto flotante para casi la igualdad , cada uno con ventajas y compensaciones específicas, y es posible que desee poder elegir qué método es el adecuado.

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

Tenga en cuenta que la página a la que me vinculé (arriba) declara explícitamente que esta prueba de casi igualdad tiene algunas debilidades. Como se trata de una IEqualityComparer<T>implementación, simplemente puede cambiarla si no es lo suficientemente buena para sus propósitos.

stakx - ya no contribuye
fuente
66
El método de comparación sugerido para probar la igualdad de doble punto está roto, ya que cualquier IEqualityComparer<T>implementación legítima debe implementar una relación de equivalencia, lo que implica que en todos los casos en que dos objetos se comparan igual a un tercero, deben compararse entre sí. Cualquier clase que implemente IEquatable<T>o IEqualityComparer<T>de una manera contraria a la anterior está rota.
supercat
55
Un mejor ejemplo serían las comparaciones entre cadenas. Tiene sentido tener un método de comparación de cadenas que considere las cadenas como iguales solo si contienen la misma secuencia de bytes, pero hay otros métodos de comparación útiles que también constituyen relaciones de equivalencia , como las comparaciones que no distinguen entre mayúsculas y minúsculas. Tenga en cuenta que un IEqualityComparer<String>que considera "hola" igual a "Hola" y "hola" debe considerar "hola" y "hola" iguales entre sí, pero para la mayoría de los métodos de comparación eso no sería un problema.
supercat
@supercat, gracias por los valiosos comentarios. Es probable que no pueda revisar mi respuesta en los próximos días, así que si lo desea, puede editarlo como mejor le parezca.
stakx - ya no contribuye el
Esto es exactamente lo que necesitaba y lo que estoy haciendo. Sin embargo, es necesario anular GetHashCode, en el que devolverá falso una vez que los valores sean diferentes. ¿Cómo manejas tu GetHashCode?
teapeng
@teapeng: No estoy seguro de qué caso de uso específico está hablando. En términos generales, GetHashCodedebería permitirle determinar rápidamente si dos valores difieren. Las reglas son aproximadamente las siguientes: (1) GetHashCode siempre debe producir el mismo código hash para el mismo valor. (2) GetHashCode debe ser rápido (más rápido que Equals). (3) GetHashCode no tiene que ser preciso (no tan preciso como Equals). Esto significa que puede producir el mismo código hash para diferentes valores. Cuanto más preciso sea, mejor, pero probablemente sea más importante mantenerlo rápido.
stakx - ya no contribuye el
27

Ya tienes la definición básica de lo que son . En resumen, si implementa IEquatable<T>en clase T, el Equalsmétodo en un objeto de tipo Tle dice si el objeto mismo (el que se está probando para la igualdad) es igual a otra instancia del mismo tipo T. Mientras que, IEqualityComparer<T>es para probar la igualdad de dos instancias de T, típicamente fuera del alcance de las instancias de T.

En cuanto a lo que son, puede ser confuso al principio. A partir de la definición, debe quedar claro que, por lo tanto, IEquatable<T>(definido en la Tpropia clase ) debería ser el estándar de facto para representar la unicidad de sus objetos / instancias. HashSet<T>, Dictionary<T, U>(teniendo en GetHashCodecuenta que también se anula), Containsen List<T>etc. haga uso de esto. Implementar IEqualityComparer<T>en Tno ayuda a los casos generales mencionados anteriormente. Posteriormente, hay poco valor para implementar IEquatable<T>en cualquier otra clase que no sea T. Esta:

class MyClass : IEquatable<T>

Rara vez tiene sentido.

Por otra parte

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

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

así es como debe hacerse.

IEqualityComparer<T>puede ser útil cuando necesita una validación personalizada de igualdad, pero no como una regla general. Por ejemplo, en una clase de Personen algún momento, es posible que deba probar la igualdad de dos personas en función de su edad. En ese caso puedes hacer:

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 probarlos, intente

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

Del mismo modo IEqualityComparer<T>en Tque no tiene sentido.

class Person : IEqualityComparer<Person>

Es cierto que esto funciona, pero no se ve bien a los ojos y derrota la lógica.

Por lo general, lo que necesitas es IEquatable<T>. También, idealmente, puede tener solo uno, IEquatable<T>mientras que múltiples IEqualityComparer<T>es posible según diferentes criterios.

Los IEqualityComparer<T>y IEquatable<T>son exactamente análogos a Comparer<T>y IComparable<T>que se utilizan para fines de comparación en lugar de igualar; un buen hilo aquí donde escribí la misma respuesta :)

nawfal
fuente
public int GetHashCode(Person obj)debería volverobj.GetHashCode()
hyankov
@HristoYankov debería regresar obj.Age.GetHashCode. editará
nawfal
Por favor, explique por qué el comparador debe hacer tal suposición acerca de cuál es el hash del objeto que compara. Su comparador no sabe cómo la Persona calcula su Hash, ¿por qué anularía eso?
hyankov
@HristoYankov Su comparador no sabe cómo la Persona calcula su Hash - Claro, es por eso que no hemos llamado directamente a person.GetHashCodeninguna parte ... ¿por qué anularía eso? - Anulamos porque el objetivo IEqualityCompareres tener una implementación de comparación diferente de acuerdo con nuestras reglas, las reglas que realmente conocemos bien.
nawfal
En mi ejemplo, necesito basar mi comparación en la Agepropiedad, así que llamo Age.GetHashCode. La edad es de tipo int, pero cualquiera que sea el tipo de contrato de código hash en .NET es eso different objects can have equal hash but equal objects cant ever have different hashes. Este es un contrato que podemos creer ciegamente. Claro, nuestros comparadores personalizados también deben cumplir con esta regla. Si alguna vez llamar someObject.GetHashCoderompe cosas, entonces es el problema con los implementadores de tipo someObject, no es nuestro problema.
nawfal
11

IEqualityComparer se utiliza cuando la igualdad de dos objetos se implementa externamente, por ejemplo, si desea definir un comparador para dos tipos para los que no tenía la fuente, o para los casos en que la igualdad entre dos cosas solo tiene sentido en un contexto limitado.

IEquatable es para el objeto en sí (el que se compara por igualdad) para implementar.

Chris Shain
fuente
Usted es el único que realmente planteó el problema "Conectar en la igualdad" (uso externo). más 1.
Royi Namir
4

Uno compara dos Ts. El otro puede compararse con otros Ts. Por lo general, solo necesitará usar uno a la vez, no ambos.

Matt Ball
fuente