¿Cuál es la diferencia entre IEquatable y simplemente anular Object.Equals ()?

185

Quiero que mi Foodclase pueda evaluar cuando sea igual a otra instancia de Food. Más tarde lo usaré contra una Lista, y quiero usar su List.Contains()método. ¿Debo implementar IEquatable<Food>o simplemente anular Object.Equals()? De MSDN:

Este método determina la igualdad mediante el uso del comparador de igualdad predeterminado, según lo definido por la implementación del objeto del método IEquatable.Equals para T (el tipo de valores en la lista).

Entonces mi siguiente pregunta es: ¿qué funciones / clases de .NET Framework utilizan Object.Equals()? ¿Debo usarlo en primer lugar?

elysium devorado
fuente
3
Una muy buena explicación aquí blogs.msdn.com/b/jaredpar/archive/2009/01/15/…
nawfal
posible duplicado de Understanding IEquatable
nawfal

Respuestas:

214

La razón principal es el rendimiento. Cuando se introdujeron los genéricos en .NET 2.0 que fueron capaces de añadir un montón de clases ordenadas tales como List<T>, Dictionary<K,V>, HashSet<T>, etc. Estas estructuras hacen un uso intensivo de GetHashCodey Equals. Pero para los tipos de valor esto requería boxeo. IEquatable<T>permite que una estructura implemente un Equalsmétodo fuertemente tipado para que no se requiera boxeo. Por lo tanto, un rendimiento mucho mejor cuando se utilizan tipos de valor con colecciones genéricas.

Los tipos de referencia no se benefician tanto, pero la IEquatable<T>implementación le permite evitar un reparto desde el System.Objectque puede marcar la diferencia si se llama con frecuencia.

Sin embargo, como se señaló en el blog de Jared Parson , aún debe implementar las anulaciones de objetos.

Josh
fuente
¿Hay algún reparto entre los tipos de referencia? Siempre pensé que los lanzamientos eran solo "declaraciones" que haces al compilador cuando asignas algunos lanzamientos no obvios de un tipo de objeto a otro. Esto es, después de compilar, que el código ni siquiera sabría que había un elenco allí.
Devorado elysium
77
Eso es cierto en C ++ pero no en los lenguajes .NET que imponen la seguridad de tipos. Hay un lanzamiento en tiempo de ejecución y si el lanzamiento no tiene éxito, se lanza una excepción. Por lo tanto, hay una pequeña penalización de tiempo de ejecución para pagar por el lanzamiento. El compilador puede optimizar las transmisiones de distancia. Por ejemplo, objeto o = (objeto) "cadena"; Pero downcasting - cadena s = (cadena) o; - Debe suceder en tiempo de ejecución.
Josh
1
Veo. ¿Por casualidad tiene algún lugar donde pueda obtener ese tipo de información "más profunda" sobre .NET? ¡Gracias!
devorado elysium
77
Recomendaría CLR a través de C # por Jeff Richter y C # en profundidad por Jon Skeet. En cuanto a los blogs, blogs Wintellect son buenos, los blogs de MSDN, etc.
Josh
¿La IEquatable<T>interfaz hace algo más que recordarle a un desarrollador que incluya un public bool Equals(T other) miembro en la clase o estructura? La presencia o ausencia de la interfaz no hace ninguna diferencia en tiempo de ejecución. La sobrecarga de Equalsparece ser todo lo que es necesario.
mikemay
48

Según el MSDN :

Si implementa IEquatable<T>, también debe anular las implementaciones de clase base de Object.Equals(Object)y GetHashCode para que su comportamiento sea coherente con el del IEquatable<T>.Equals método. Si anula Object.Equals(Object), su implementación anulada también se llama en llamadas al Equals(System.Object, System.Object)método estático en su clase. Esto asegura que todas las invocaciones del Equalsmétodo devuelvan resultados consistentes.

Por lo tanto, parece que no hay una diferencia funcional real entre los dos, excepto que cualquiera podría llamarse dependiendo de cómo se use la clase. Desde el punto de vista del rendimiento, es mejor usar la versión genérica porque no hay penalización de boxeo / desempaquetado asociada con ella.

Desde un punto de vista lógico, también es mejor implementar la interfaz. Anular el objeto realmente no le dice a nadie que su clase es realmente equiparable. La anulación puede ser una clase de no hacer nada o una implementación superficial. El uso de la interfaz dice explícitamente: "¡Oye, esto es válido para la verificación de igualdad!" Es solo un mejor diseño.

CodexArcanum
fuente
9
Las estructuras definitivamente deberían implementar iEquatable (de su propio tipo) si se van a utilizar como claves en un diccionario o colección similar; Ofrecerá un gran impulso de rendimiento. Las clases no heredables recibirán un ligero aumento de rendimiento al implementar IEquatable (de su propio tipo). Las clases heredables deberían // no // implementar IEquatable.
supercat
30

Extendiendo lo que Josh dijo con un ejemplo práctico. +1 a Josh: estaba a punto de escribir lo mismo en mi respuesta.

public abstract class EntityBase : IEquatable<EntityBase>
{
    public EntityBase() { }

    #region IEquatable<EntityBase> Members

    public bool Equals(EntityBase other)
    {
        //Generic implementation of equality using reflection on derived class instance.
        return true;
    }

    public override bool Equals(object obj)
    {
        return this.Equals(obj as EntityBase);
    }

    #endregion
}

public class Author : EntityBase
{
    public Author() { }
}

public class Book : EntityBase
{
    public Book() { }
}

De esta manera, tengo el método Equals () reutilizable que funciona de forma inmediata para todas mis clases derivadas.

esta. __curious_geek
fuente
Solo una pregunta mas. ¿Cuál es la ventaja de usar "obj como EntityBase" en lugar de (EntityBase) obj? ¿Es solo cuestión de estilo o hay alguna ventaja?
devorado elysium
22
en caso de "obj como EntityBase" - si obj no es del tipo EntityBase, pasará "nulo" y continuará sin ningún error o excepción, pero en caso de "(EntityBase) obj", intentará forzar el obj a EntityBase y si el obj no es del tipo EntityBase, arrojará InvalidCastException. Y sí, "as" solo se puede aplicar a los tipos de referencia.
esto. __curious_geek
1
El enlace de Josh al blog de Jared Par parece sugerir que también debe anular GetHashCode. ¿No es este el caso?
Amistoso
3
Realmente no obtengo el valor extra que proporciona su implementación. ¿Puedes aclarar el problema que resuelve tu clase base abstracta?
Mert Akcakaya
1
@Amicable: sí, siempre que anule Object.Equals (Object), también debe anular GetHashCode para que los contenedores funcionen.
namford
0

Si llamamos object.Equals, obliga al costoso boxeo en los tipos de valor. Esto no es deseable en escenarios sensibles al rendimiento. La solución es usar IEquatable<T>.

public interface IEquatable<T>
{
  bool Equals (T other);
}

La idea detrás IEquatable<T>es que da el mismo resultado object.Equalspero más rápido. La restricción where T : IEquatable<T>debe usarse con tipos genéricos como a continuación.

public class Test<T> where T : IEquatable<T>
{
  public bool IsEqual (T a, T b)
  {
    return a.Equals (b); // No boxing with generic T
  }
}

de lo contrario, se une a slower object.Equals().

Seyedraouf Modarresi
fuente