c# .net generics comparison iequalitycomparer

c# - EqualityComparer<T>.Default vs. T.Equals



.net generics (5)

Supongamos que tengo una MyClass<T> genérica que necesita comparar dos objetos de tipo <T> . Usualmente yo haría algo como ...

void DoSomething(T o1, T o2) { if(o1.Equals(o2)) { ... } }

Ahora suponga que mi MyClass<T> tiene un constructor que admite pasar un IEqualityComparer<T> , similar al Dictionary<T> . En ese caso tendría que hacer ...

private IEqualityComparer<T> _comparer; public MyClass() {} public MyClass(IEqualityComparer<T> comparer) { _comparer = comparer; } void DoSomething(T o1, T o2) { if((_comparer != null && _comparer.Equals(o1, o2)) || (o1.Equals(o2))) { ... } }

Para eliminar esta declaración if larga, sería bueno si pudiera tener _comparer predeterminado en un ''comparador predeterminado'' si se usa el constructor normal. Busqué algo como typeof(T).GetDefaultComparer() pero no pude encontrar nada igual.

Encontré EqualityComparer<T>.Default , ¿podría usar eso? Y luego este fragmento ...

public MyClass() { _comparer = EqualityComparer<T>.Default; } void DoSomething(T o1, T o2) { if(_comparer.Equals(o1, o2)) { ... } }

... proporciona los mismos resultados que usando o1.Equals(o2) para todos los casos posibles?

(Como nota al margen, ¿esto significaría que también necesitaría usar alguna restricción genérica especial para <T> ?)


¿Podrías usar el operador coaelescense nulo? para acortar el si si realmente importa

if ((_comparer ?? EqualityComparer<T>.Default).Equals(o1, o2)) { }


De forma predeterminada, hasta que se invalide en una clase, Object.Equals(a,b) / a.Equals(b) realiza una comparación por referencia.

El comparador será devuelto por EqualityComparer<T>.Default depende de T Por ejemplo, si T : IEquatable<> se T : IEquatable<> el EqualityComparer<T> apropiado.


Debería ser el mismo, pero no está garantizado, ya que depende de los detalles de implementación del tipo T
Explicación:
Sin una restricción a T , o1.Equals (o2) llamará a Object.Equals , incluso si T implementa IEquatable<T> .
EqualityComparer<T>.Default embargo, utilizará Object.Equals solamente, si T no implementa IEquatable<T> . Si implementa esa interfaz, utiliza IEquatable<T>.Equals .
Siempre y cuando la implementación de Object.Equals de Object.Equals solo llame a IEquatable<T>.Equals el resultado es el mismo. Pero en el siguiente ejemplo, el resultado no es el mismo:

public class MyObject : IEquatable<MyObject> { public int ID {get;set;} public string Name {get;set;} public override bool Equals(object o) { var other = o as MyObject; return other == null ? false : other.ID == ID; } public bool Equals(MyObject o) { return o.Name == Name; } }

Ahora, no tiene ningún sentido implementar una clase como esta. Pero tendrá el mismo problema, si el implementador de MyObject simplemente olvidó anular Object.Equals .

Conclusión:
Usar EqualityComparer<T>.Default es una buena forma de EqualityComparer<T>.Default , ¡porque no es necesario que soporte objetos con errores!


Eso es exactamente lo que hace Dictionary<> y otras colecciones genéricas en el BCL si no especifica un comparador al construir el objeto. La ventaja de esto es que EqualityComparer<T>.Default devolverá el comparador correcto para los IEquatable<T> , tipos anulables y enumeraciones de IEquatable. Si T no es ninguno de ellos, hará una comparación simple de Igual a como lo está haciendo el código anterior.


Sí, creo que sería prudente usar EqualityComparer<T>.Default , porque usa la implementación de IEquatable<T> si el tipo T implementa, o la anulación de Object.Equals contrario. Podrías hacerlo de la siguiente manera:

private IEqualityComparer<T> _comparer; public IEqualityComparer<T> Comparer { get { return _comparer?? EqualityComparer<T>.Default;} set { _comparer=value;} } public MyClass(IEqualityComparer<T> comparer) { _comparer = comparer; } void DoSomething(T o1, T o2) { if(Comparer.Equals(o1, o2)) { ... } }