¿Por qué es importante anular GetHashCode cuando se anula el método Equals?

Resuelto David Basarab asked hace 16 años • 16 respuestas

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;

        if (fooItem == null) 
        {
           return false;
        }

        return fooItem.FooId == this.FooId;
    }

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

        return base.GetHashCode();

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

He anulado el Equalsmétodo porque Foorepresenta una fila para la Footabla s. ¿ Cuál es el método preferido para anular GetHashCode?

¿ Por qué es importante anular GetHashCode?

David Basarab avatar Dec 16 '08 20:12 David Basarab
Aceptado

Sí, es importante si su elemento se usará como clave en un diccionario, o HashSet<T>, etc., ya que se usa (en ausencia de un elemento personalizado IEqualityComparer<T>) para agrupar elementos en depósitos. Si el código hash de dos elementos no coincide, es posible que nunca se consideren iguales ( simplemente nunca se llamará Equals ).

El método GetHashCode() debería reflejar la Equalslógica; las reglas son:

  • si dos cosas son iguales ( Equals(...) == true) entonces deben devolver el mismo valor paraGetHashCode()
  • si son iguales, noGetHashCode() es necesario que sean iguales; esto es una colisión, y se llamará para ver si es una igualdad real o no.Equals

En este caso, parece que " return FooId;" es una GetHashCode()implementación adecuada. Si está probando varias propiedades, es común combinarlas usando código como el siguiente, para reducir las colisiones diagonales (es decir, para que new Foo(3,5)tenga un código hash diferente new Foo(5,3)):

En los marcos modernos, el HashCodetipo tiene métodos para ayudarlo a crear un código hash a partir de múltiples valores; en marcos más antiguos, tendrías que prescindir de ellos, por lo que algo como:

unchecked // only needed if you're compiling with arithmetic checks enabled
{ // (the default compiler behaviour is *disabled*, so most folks won't need this)
    int hash = 13;
    hash = (hash * 7) + field1.GetHashCode();
    hash = (hash * 7) + field2.GetHashCode();
    ...
    return hash;
}

Ah, por conveniencia, también podría considerar proporcionar operadores ==and !=al anular Equalsand GetHashCode.


Aquí encontrará una demostración de lo que sucede cuando se equivoca .

Marc Gravell avatar Dec 16 '2008 13:12 Marc Gravell

En realidad, es muy difícil implementarlo GetHashCode()correctamente porque, además de las reglas que Marc ya mencionó, el código hash no debería 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 configurar a través del constructor, por lo que si desea cambiar el ID, lo cual 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 GUID porque puede proporcionar un constructor sin parámetros que genera una identificación aleatoriamente.

Albic avatar Dec 21 '2008 12:12 Albic

Al anular, Equalsbásicamente estás afirmando que sabes mejor cómo comparar dos instancias de un tipo determinado.

A continuación puede ver un ejemplo de cómo ReSharper escribe una GetHashCode()función por usted. Tenga en cuenta que este fragmento debe ser modificado por el programador:

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, simplemente intenta adivinar un buen código hash basado en todos los campos de la clase, pero si conoce el dominio o los rangos de valores de su objeto, aún podría proporcionar uno mejor.

Trap avatar Dec 16 '2008 13:12 Trap

A continuación se muestra .NET 4.7el método preferido de anulación . GetHashCode()Si se dirige a versiones anteriores de .NET, incluya el paquete nuget System.ValueTuple .

// C# 7.0+
public override int GetHashCode() => (FooId, FooName).GetHashCode();

En términos de rendimiento, este método superará a la mayoría de las implementaciones de código hash compuesto . ValueTuple es así, no habrá basura y el algoritmo subyacente es lo más rápido posible .struct

l33t avatar May 11 '2020 12:05 l33t