¿Por qué es importante anular GetHashCode cuando se anula el método Equals?
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 Equals
método porque Foo
representa una fila para la Foo
tabla s. ¿ Cuál es el método preferido para anular GetHashCode
?
¿ Por qué es importante anular GetHashCode
?
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 Equals
lógica; las reglas son:
- si dos cosas son iguales (
Equals(...) == true
) entonces deben devolver el mismo valor paraGetHashCode()
- si son iguales, no
GetHashCode()
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 HashCode
tipo 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 Equals
and GetHashCode
.
Aquí encontrará una demostración de lo que sucede cuando se equivoca .
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.
Al anular, Equals
bá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.
A continuación se muestra .NET 4.7
el 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