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 el GetHashCode
?
¿Por qué es importante anular GetHashCode
?
c#
overriding
hashcode
David Basarab
fuente
fuente
Respuestas:
Sí, es importante si su elemento se utilizará como clave en un diccionario,
HashSet<T>
etc., ya que se utiliza (en ausencia de una costumbreIEqualityComparer<T>
) para agrupar elementos en cubos. Si el código hash para dos elementos no coincide, es posible que nunca se consideren iguales ( simplemente no se llamarán iguales ).El método GetHashCode () debe reflejar la
Equals
lógica; las reglas son:Equals(...) == true
), entonces deben devolver el mismo valor paraGetHashCode()
GetHashCode()
es igual, es que no es necesario que sean los mismos; Esta es una colisión, yEquals
se llamará para ver si es una igualdad real o no.En este caso, parece que "
return FooId;
" es unaGetHashCode()
implementación adecuada . Si está probando varias propiedades, es común combinarlas usando el código que se muestra a continuación, para reducir las colisiones diagonales (es decir, para quenew Foo(3,5)
tenga un código hash diferentenew Foo(5,3)
):Ah, por conveniencia, también puede considerar proporcionar
==
y!=
operadores al anularEquals
yGetHashCode
.Una demostración de lo que sucede cuando te equivocas está aquí .
fuente
En realidad, es muy difícil de implementar
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 estaba trabajando con NHibernate. Mi enfoque es calcular el código hash a partir de la ID del objeto. La ID solo se puede establecer a través del constructor, por lo que si desea cambiar la ID, lo cual es muy poco probable, debe crear un nuevo objeto que tenga una nueva 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 genera aleatoriamente una ID.
fuente
Al anular Equals, básicamente estás afirmando que eres el que sabe mejor cómo comparar dos instancias de un tipo determinado, 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:
Como puede ver, solo trata de adivinar un buen código hash basado en todos los campos de la clase, pero dado que conoce el dominio o los rangos de valores de su objeto, aún podría proporcionar uno mejor.
fuente
0 ^ a = a
, entonces0 ^ m_someVar1 = m_someVar1
. También podría establecer el valor inicial deresult
tom_someVar1
.No olvide comprobar el parámetro obj en contra
null
al anularEquals()
. Y también compara el tipo.La razón de esto es:
Equals
debe devolver falso en comparación connull
. Ver también http://msdn.microsoft.com/en-us/library/bsc2ak47.aspxfuente
obj
hecho es igual athis
no importa cómo se llamó a Equals () de la clase base.fooItem
hacia arriba y luego verificar que sea nulo funcionará mejor en el caso de nulo o un tipo incorrecto.obj as Foo
sería inválido.Qué tal si:
fuente
string.Format
. Otro geek que he visto esnew { prop1, prop2, prop3 }.GetHashCode()
. Sin embargo, no puedo comentar cuál sería más lento entre estos dos. No abuses de las herramientas.{ prop1="_X", prop2="Y", prop3="Z" }
y{ prop1="", prop2="X_Y", prop3="Z_" }
. Probablemente no quieras eso.Tenemos dos problemas que enfrentar.
No puede proporcionar un sensible
GetHashCode()
si cualquier campo en el objeto se puede cambiar. También, a menudo, un objeto NUNCA se usará en una colección de la que dependeGetHashCode()
. Por lo tanto, el costo de implementación aGetHashCode()
menudo no vale la pena, o no es posible.Si alguien coloca su objeto en una colección que llama
GetHashCode()
y usted ha anuladoEquals()
sin hacer que seGetHashCode()
comporte de manera correcta, esa persona puede pasar días rastreando el problema.Por lo tanto, por defecto lo hago.
fuente
GetHashCode
función de modo que dos objetos que sean iguales devuelvan el mismo código hash;return 24601;
yreturn 8675309;
ambos serían implementaciones válidas deGetHashCode
. El rendimiento deDictionary
solo será decente cuando el número de elementos es pequeño, y se volverá muy malo si el número de elementos aumenta, pero en cualquier caso funcionará correctamente.Esto se debe a que el marco requiere que dos objetos que sean iguales tengan el mismo código hash. Si anula el método de igualdad para hacer una comparación especial de dos objetos y el método considera que los dos objetos son iguales, entonces el código hash de los dos objetos también debe ser el mismo. (Los diccionarios y las tablas hash se basan en este principio).
fuente
Solo para agregar las respuestas anteriores:
Si no anula Equals, el comportamiento predeterminado es que se comparan las referencias de los objetos. Lo mismo se aplica al código hash: la implicación predeterminada generalmente se basa en una dirección de memoria de la referencia. Debido a que reemplazó a Equals, significa que el comportamiento correcto es comparar lo que haya implementado 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 costoso para ejecutar, si no implementamos el código hash, el objeto igual probablemente tendrá códigos hash diferentes (porque tienen una dirección de memoria diferente) y se determinará erróneamente como no igual (Equals () ni siquiera golpeará).
Además, excepto el problema de que 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 busca, el código hash predeterminado probablemente será diferente y nuevamente Equals () ni siquiera será llamado, como explica Marc Gravell en su respuesta, también introduce una violación del concepto de diccionario o hashset que no debería permitir claves idénticas: ya declaró que esos objetos son esencialmente los mismos cuando anula Iguales, por lo que no No los quiero a ambos como claves diferentes en una estructura de datos que supone tener una clave única. Pero debido a que tienen un código hash diferente, la clave "misma" se insertará como una clave diferente.
fuente
El código hash se usa para colecciones basadas en hash como Dictionary, Hashtable, HashSet, etc. El propósito de este código es ordenar previamente muy rápidamente un objeto específico colocándolo en un grupo específico (bucket). Esta clasificación previa ayuda enormemente a encontrar este objeto cuando necesita recuperarlo de la colección hash porque el código tiene que buscar su objeto en un solo cubo en lugar de en todos los objetos que contiene. La mejor distribución de los códigos hash (mejor singularidad) la recuperación más rápida. En una situación ideal donde cada objeto tiene un código hash único, encontrarlo es una operación O (1). En la mayoría de los casos se acerca a O (1).
fuente
No es necesariamente importante; depende del tamaño de sus colecciones y sus requisitos de rendimiento y de si su clase se utilizará en una biblioteca donde es posible que no conozca los requisitos de rendimiento. Con frecuencia sé que los tamaños de mi colección no son muy grandes y mi tiempo es más valioso que unos pocos microsegundos de rendimiento obtenidos al crear un código hash perfecto; entonces (para deshacerme de la molesta advertencia del compilador) simplemente uso:
(Por supuesto, también podría usar un #pragma para desactivar la advertencia, pero prefiero de esta manera).
Cuando se encuentre en la posición que usted no necesita el rendimiento de todos los problemas mencionados por otros aquí se aplican, por supuesto. Lo más importante : de lo contrario, obtendrá resultados incorrectos cuando recupere elementos de un conjunto o diccionario de hash : el código de hash no debe variar con la vida útil de un objeto (más exactamente, durante el tiempo cada vez que se necesita el código de hash, como una clave en un diccionario): por ejemplo, lo siguiente es incorrecto ya que Value es público y, por lo tanto, se puede cambiar externamente a la clase durante el tiempo de vida de la instancia, por lo que no debe usarlo como base para el código hash:
Por otro lado, si el valor no se puede cambiar, está bien usarlo:
fuente
Siempre debe garantizar que si dos objetos son iguales, según lo definido por Equals (), deberían devolver el mismo código hash. Como dicen algunos de los otros comentarios, en teoría esto no es obligatorio si el objeto nunca se usará en un contenedor basado en hash como HashSet o Dictionary. Sin embargo, te aconsejaría que sigas siempre esta regla. La razón es simplemente porque es demasiado fácil para alguien cambiar una colección de un tipo a otro con la buena intención de mejorar realmente el rendimiento o simplemente transmitir la semántica del código de una mejor manera.
Por ejemplo, supongamos que mantenemos algunos objetos en una Lista. Algún tiempo después, alguien se da cuenta de que un HashSet es una alternativa mucho mejor debido a las mejores características de búsqueda, por ejemplo. Aquí es cuando podemos meternos en problemas. List utilizaría internamente el comparador de igualdad predeterminado para el tipo, lo que significa Igual en su caso, mientras que HashSet utiliza GetHashCode (). Si los dos se comportan de manera diferente, también lo hará su programa. Y tenga en cuenta que tales problemas no son los más fáciles de solucionar.
He resumido este comportamiento con algunas otras dificultades de GetHashCode () en una publicación de blog donde puede encontrar más ejemplos y explicaciones.
fuente
A partir del
.NET 4.7
método preferido de anulaciónGetHashCode()
se muestra a continuación. Si apunta a versiones anteriores de .NET, incluya el paquete nuget System.ValueTuple .En términos de rendimiento, este método superará a la mayoría de las implementaciones de código hash compuesto . El ValueTuple es un
struct
modo que no habrá ninguna basura, y el algoritmo subyacente es tan rápido como es posible.fuente
Tengo entendido que el GetHashCode original () devuelve la dirección de memoria del objeto, por lo que es esencial anularlo si desea comparar dos objetos diferentes.
EDITADO: Eso fue incorrecto, el método GetHashCode () original no puede asegurar la igualdad de 2 valores. Aunque los objetos que son iguales devuelven el mismo código hash.
fuente
A continuación, usar la reflexión me parece una mejor opción teniendo en cuenta las propiedades públicas, ya que con esto no tiene que preocuparse por la adición / eliminación de propiedades (aunque no es un escenario tan común). Esto me pareció funcionar mejor también. (Tiempo comparado usando cronómetro de Diagonistics).
fuente