c# - visual - ¿Por qué es importante anular GetHashCode cuando se invalida el método Equals?




override equals c# (8)

A continuación, usar la reflexión me parece una mejor opción considerando las propiedades públicas, ya que con esto no tiene que preocuparse por agregar / eliminar propiedades (aunque no es un escenario tan común). También encontré que este rendimiento es mejor (el tiempo comparado con el cronómetro de Diagonistics).

    public int getHashCode()
    {
        PropertyInfo[] theProperties = this.GetType().GetProperties();
        int hash = 31;
        foreach (PropertyInfo info in theProperties)
        {
            if (info != null)
            {
                var value = info.GetValue(this,null);
                if(value != null)
                unchecked
                {
                    hash = 29 * hash ^ value.GetHashCode();
                }
            }
        }
        return hash;  
    }

Dada la siguiente clase

public class Foo
{
    public int FooId { get; set; }
    public string FooName { get; set; }

    public override bool Equals(object obj)
    {
        Foo fooItem = obj as Foo;

        return fooItem.FooId == this.FooId;
    }

    public override int GetHashCode()
    {
        // Which is preferred?

        return base.GetHashCode();

        //return this.FooId.GetHashCode();
    }
}

He anulado el método Equals porque Foo representa una fila para la tabla de Foo . ¿Cuál es el método preferido para anular el GetHashCode ?

¿Por qué es importante anular GetHashCode ?


Al anular Equals, básicamente estás diciendo que eres el que sabe mejor cómo comparar dos instancias de un tipo dado, por lo que es probable que seas el mejor candidato para proporcionar el mejor código hash.

Este es un ejemplo de cómo ReSharper escribe una función GetHashCode () para usted:

public override int GetHashCode()
{
    unchecked
    {
        var result = 0;
        result = (result * 397) ^ m_someVar1;
        result = (result * 397) ^ m_someVar2;
        result = (result * 397) ^ m_someVar3;
        result = (result * 397) ^ m_someVar4;
        return result;
    }
}

Como puede ver, solo trata de adivinar un buen código hash basado en todos los campos de la clase, pero como sabe el dominio de su objeto o los rangos de valores, aún podría proporcionar uno mejor.


En realidad, es muy difícil implementar GetHashCode() correctamente porque, además de las reglas que Marc ya mencionó, el código hash no debe cambiar durante la vida útil de un objeto. Por lo tanto, los campos que se utilizan para calcular el código hash deben ser inmutables.

Finalmente encontré una solución a este problema cuando trabajaba con NHibernate. Mi enfoque es calcular el código hash a partir del ID del objeto. El ID solo se puede establecer a través del constructor, por lo que si desea cambiar el ID, que es muy poco probable, debe crear un nuevo objeto que tenga un nuevo ID y, por lo tanto, un nuevo código hash. Este enfoque funciona mejor con los GUID porque puede proporcionar un constructor sin parámetros que genere aleatoriamente una ID.


Esto se debe a que el marco requiere que dos objetos que sean iguales tengan el mismo código hash. Si reemplaza el método equals para hacer una comparación especial de dos objetos y los dos objetos son considerados iguales por el método, entonces el código hash de los dos objetos también debe ser el mismo. (Los diccionarios y Hashtables se basan en este principio).


No olvide verificar el parámetro obj con el null al anular Equals() . Y también comparar el tipo.

public override bool Equals(object obj)
{
    if (obj == null || GetType() != obj.GetType())
        return false;

    Foo fooItem = obj as Foo;

    return fooItem.FooId == this.FooId;
}

El motivo de esto es: Equals debe devolver false en comparación con null . Consulte también http://msdn.microsoft.com/en-us/library/bsc2ak47.aspx


Qué tal si:

public override int GetHashCode()
{
    return string.Format("{0}_{1}_{2}", prop1, prop2, prop3).GetHashCode();
}

Suponiendo que el rendimiento no es un problema :)


Sólo para agregar en las respuestas anteriores:

Si no reemplaza a Equals, entonces el comportamiento predeterminado es que se comparan las referencias de los objetos. Lo mismo se aplica al código hash: la implementación predeterminada generalmente se basa en una dirección de memoria de la referencia. Como anuló Igals, significa que el comportamiento correcto es comparar lo que implementó en Equals y no las referencias, por lo que debe hacer lo mismo para el código hash.

Los clientes de su clase esperarán que el código hash tenga una lógica similar al método equals, por ejemplo, los métodos linq que usan un IEqualityComparer primero comparan los códigos hash y solo si son iguales, compararán el método Equals () que podría ser más caro para ejecutar, si no implementamos el código hash, el objeto igual probablemente tendrá diferentes códigos hash (porque tienen una dirección de memoria diferente) y se determinará erróneamente como no igual (Igual () no llegará).

Además, excepto por el problema, es posible que no pueda encontrar su objeto si lo usó en un diccionario (porque fue insertado por un código hash y cuando lo busque, el código hash predeterminado probablemente será diferente y nuevamente el mismo) ni siquiera se llamará, como explica Marc Gravell en su respuesta, también introduces una violación del concepto de diccionario o hashset que no debería permitir claves idénticas: ya declaraste que esos objetos son esencialmente iguales cuando anulas Equals por lo que no No desee que ambas sean claves diferentes en una estructura de datos que suponga tener una clave única, pero como tienen un código hash diferente, la clave "igual" se insertará como una clave diferente.


Tenemos dos problemas para hacer frente.

  1. No puede proporcionar un GetHashCode() sensible si se puede cambiar cualquier campo en el objeto. También a menudo un objeto NUNCA se utilizará en una colección que depende de GetHashCode() . Entonces, el costo de implementar GetHashCode() menudo no vale la pena, o no es posible.

  2. Si alguien coloca su objeto en una colección que llama a GetHashCode() y usted ha anulado Equals() sin hacer que GetHashCode() comporte de manera correcta, esa persona puede pasar días rastreando el problema.

Por lo tanto, por defecto lo hago.

public class Foo
{
    public int FooId { get; set; }
    public string FooName { get; set; }

    public override bool Equals(object obj)
    {
        Foo fooItem = obj as Foo;

        return fooItem.FooId == this.FooId;
    }

    public override int GetHashCode()
    {
        // Some comment to explain if there is a real problem with providing GetHashCode() 
        // or if I just don't see a need for it for the given class
        throw new Exception("Sorry I don't know what GetHashCode should do for this class");
    }
}






hashcode