Pregunta ¿Se debe implementar IEquatable , IComparable en clases no selladas?


Alguien tiene alguna opinión sobre si o no IEquatable<T> o IComparable<T> generalmente debe requerir que T es sealed (si es un class)?

Esta pregunta se me ocurrió porque estoy escribiendo un conjunto de clases base destinadas a ayudar en la implementación de clases inmutables. Parte de la funcionalidad que la clase base pretende proporcionar es la implementación automática de comparaciones de igualdad (usando los campos de la clase junto con los atributos que se pueden aplicar a los campos para controlar las comparaciones de igualdad). Debería ser muy agradable cuando termine. Estoy usando árboles de expresiones para crear dinámicamente una función de comparación compilada para cada uno. T, por lo que la función de comparación debe estar muy cerca del rendimiento de una función de comparación de igualdad regular. (Estoy usando un diccionario inmutable codificado en System.Type y doble control de bloqueo para almacenar las funciones de comparación generadas de una manera razonablemente efectiva)

Una cosa que ha surgido, sin embargo, es qué funciones usar para verificar la igualdad de los campos miembros. Mi intención inicial era verificar si el tipo de campo de cada miembro (al que llamaré X) implementa IEquatable<X>. Sin embargo, después de pensarlo un poco, no creo que esto sea seguro de usar a menos que X es sealed. La razón es que si X no es sealed, No puedo saber con certeza si X está delegando apropiadamente las verificaciones de igualdad a un método virtual en X, lo que permite que un subtipo anule la comparación de igualdad.

Esto genera una pregunta más general: si un tipo no está sellado, ¿realmente debería implementar estas interfaces? Yo diría que no, ya que argumentaría que el contrato de interfaces es para comparar dos X tipos, no dos tipos que pueden o no ser X (aunque deben, por supuesto, ser X o un subtipo).

¿Qué piensan ustedes? Debería IEquatable<T> y IComparable<T> evitarse para las clases sin sellar? (También me pregunto si hay una regla fxcop para esto)

Mi pensamiento actual es hacer que mi función de comparación generada solo use IEquatable<T> en los campos miembros cuyo T es sealed, y en su lugar usar el virtual Object.Equals(Object obj) Si T está abierto incluso si T implementos IEquatable<T>, ya que el campo podría potencialmente almacenar subtipos de T y dudo que la mayoría de las implementaciones de IEquatable<T> están diseñados apropiadamente para la herencia.


32
2017-12-08 16:56


origen


Respuestas:


He estado pensando en esta pregunta por un tiempo y después de un poco de consideración, acepto que implementar IEquatable<T> y IComparable<T> solo debe hacerse en tipos sellados.

Estuve yendo y viniendo por un tiempo, pero luego pensé en la siguiente prueba. ¿Bajo qué circunstancias debería volverse falso lo siguiente? En mi humilde opinión, 2 objetos son iguales o no lo son.

public void EqualitySanityCheck<T>(T left, T right) where T : IEquatable<T> {
  var equals1 = left.Equals(right);
  var equals2 = ((IEqutable<T>)left).Equals(right);
  Assert.AreEqual(equals1,equals2);
}

El resultado de IEquatable<T> en un objeto dado debe tener el mismo comportamiento que Object.Equals suponiendo que el comparador es del tipo equivalente. Implementar IEquatable<T> dos veces en una jerarquía de objetos permite, e implica, que hay dos formas diferentes de expresar la igualdad en su sistema. Es fácil idear cualquier cantidad de escenarios donde IEquatable<T> y Object.Equalssería diferente ya que hay múltiples IEquatable<T> implementaciones, pero solo un solo Object.Equals. Por lo tanto, lo anterior fallaría y crearía un poco de confusión en su código.

Algunas personas pueden argumentar que implementar IEquatable<T> en un punto más alto en la jerarquía de objetos es válido porque quiere comparar un subconjunto de las propiedades de los objetos. En ese caso, deberías favorecer IEqualityComparer<T> que está específicamente diseñado para comparar esas propiedades.


14
2017-12-08 17:21



En general, recomendaría no implementar IEquatable <T> en ninguna clase no sellada, ni implementar IComparable no genérico en la mayoría, pero no se puede decir lo mismo de IComparable <T>. Dos razones:

  1. Ya existe un medio para comparar objetos que pueden ser o no del mismo tipo: Object.Equals. Como IEtabletable <T> no incluye GetHashCode, su comportamiento esencialmente tiene que coincidir con Object.Equals. La única razón para implementar IEquatable <T> además de Object.Equals es el rendimiento. IEquatable <T> ofrece una pequeña mejora de rendimiento frente a Object.Equals cuando se aplica a tipos de clase sellados, y una gran mejora cuando se aplica a los tipos de estructura. La única forma en que la implementación de tipo abierto de IEquatable <T> .Equals puede garantizar que su comportamiento coincida con el de un Object.Equals posiblemente anulado, sin embargo, es llamar a Object.Equals. Si IEtabletable <T> .Equals tiene que llamar Object.Equals, cualquier posible ventaja de rendimiento desaparece.
  2. A veces es posible, significativo y útil que una clase base tenga un orden natural definido que involucre solo propiedades de clase base, que será consistente a través de todas las subclases. Al verificar la igualdad de dos objetos, el resultado no debe depender de si uno considera los objetos como un tipo base o un tipo derivado. Sin embargo, al clasificar los objetos, el resultado a menudo debe depender del tipo que se utilice como base para la comparación. Los objetos de clase derivada deben implementar IComparable <TheirOwnType> pero no deben anular el método de comparación del tipo de base. Es completamente razonable que dos objetos de clase derivada se puedan comparar como "sin clasificar" cuando se comparan con el tipo padre, pero que uno se compare por encima del otro cuando se compara con el tipo derivado.

La implementación de IComparable no genérico en clases heredables es quizás más cuestionable que la implementación de IComparable <T>. Probablemente lo mejor que se puede hacer es permitir que una clase base lo implemente si no se espera que cualquier clase secundaria necesite algún otro orden, sino que las clases secundarias no reimplementen o anulen las implementaciones de la clase padre.


4
2017-10-07 21:30



Más Equals Las implementaciones que he visto comprueban los tipos de objetos que se comparan, si no son iguales, el método devuelve falso.

Esto evita claramente el problema de que un subtipo se compare con su tipo padre, lo que anula la necesidad de sellar una clase.

Un ejemplo obvio de esto sería tratar de comparar un punto 2D (A) con un punto 3D (B): para un 2D, los valores x e y de un punto 3D pueden ser iguales, pero para un punto 3D, el valor z será muy probablemente sea diferente.

Esto significa que A == B sería cierto, pero B == A sería falso A la mayoría de las personas les gusta Equals operadores para ser conmutativos, para verificar tipos es claramente una buena idea en este caso.

¿Pero qué pasa si subclases y no agrega ninguna propiedad nueva? Bueno, eso es un poco más difícil de responder, y posiblemente dependa de tu situación.


1
2017-12-09 14:29