Consejos generales y directrices sobre cómo sobrescribir correctamente object.GetHashCode ()

Según MSDN , una función hash debe tener las siguientes propiedades:

  1. Si dos objetos se comparan como iguales, el método GetHashCode para cada objeto debe devolver el mismo valor. Sin embargo, si dos objetos no se pueden comparar como iguales, los métodos GetHashCode para los dos objetos no tienen que devolver valores diferentes.

  2. El método GetHashCode para un objeto debe devolver consistentemente el mismo código hash siempre que no haya ninguna modificación en el estado del objeto que determine el valor de retorno del método Equals del objeto. Tenga en cuenta que esto es cierto solo para la ejecución actual de una aplicación, y que se puede devolver un código hash diferente si la aplicación se ejecuta nuevamente.

  3. Para obtener el mejor rendimiento, una función hash debe generar una distribución aleatoria para todas las entradas.


Sigo encontrándome en la siguiente situación: he creado una clase, implementé IEquatable y object.Equals(object) . MSDN afirma que:

Los tipos que anulan Iguales también deben anular GetHashCode; de lo contrario, Hashtable podría no funcionar correctamente.

Y luego generalmente se detiene un poco para mí. Porque, ¿cómo sobrescribe correctamente object.GetHashCode() ? Nunca sé por dónde empezar, y parece que hay muchas trampas.

Aquí en StackOverflow, hay bastantes preguntas relacionadas con Overriding de GetHashCode, pero la mayoría de ellas parecen estar en casos bastante particulares y problemas específicos. Por lo tanto, me gustaría obtener una buena comstackción aquí. Una visión general con consejos y pautas generales. Qué hacer, qué no hacer, peligros comunes, por dónde empezar, etc.

Me gustaría que esté especialmente dirigido a C #, pero creo que funcionará de la misma manera para otros lenguajes .NET también (?).


Creo que quizás la mejor manera es crear una respuesta por tema con una respuesta rápida y corta primero (cerca de una línea si es posible), luego tal vez más información y terminar con preguntas relacionadas, discusiones, publicaciones en blogs, etc. , si hay alguno. Luego puedo crear una publicación como la respuesta aceptada (para obtenerla en la parte superior) con solo una “tabla de contenido”. Trate de mantenerlo corto y conciso. Y no solo enlaces a otras preguntas y publicaciones en el blog. Intenta tomar la esencia de ellos y luego vincularlos a la fuente (especialmente porque la fuente podría desaparecer. Además, intenta editar y mejorar las respuestas en lugar de crear muchas muy similares.

No soy un escritor técnico muy bueno, pero al menos intentaré formatear las respuestas para que se parezcan, crear la tabla de contenido, etc. También intentaré buscar algunas de las preguntas relacionadas aquí en SO que responde partes de estos y tal vez sacar la esencia de los que puedo administrar. Pero como no soy muy estable en este tema, trataré de mantenerme alejado en su mayor parte: p

Tabla de contenido

  • ¿Cuándo object.GetHashCode ?

  • ¿Por qué tengo que anular object.GetHashCode ()?

  • ¿Cuáles son esos números mágicos vistos en las implementaciones de GetHashCode?


Cosas que me gustaría que se cubrieran, pero aún no han sido:

  • Cómo crear el entero (Cómo “convertir” un objeto en un int. No era muy obvio para mí de todos modos).
  • En qué campos basar el código hash.
    • Si solo debe estar en campos inmutables, ¿qué pasa si solo hay campos mutables?
  • Cómo generar una buena distribución aleatoria. (Propiedad de MSDN n. ° 3)
    • En parte, parece elegir un buen número mágico principal (se han usado 17, 23 y 397), pero ¿cómo lo eliges y para qué sirve exactamente?
  • Cómo asegurarse de que el código hash permanezca igual durante toda la vida del objeto. (Propiedad MSDN n. ° 2)
    • Especialmente cuando la igualdad se basa en campos mutables. (Propiedad de MSDN n. ° 1)
  • Cómo tratar con campos que son tipos complejos (no entre los tipos de C # integrados ).
    • Objetos y estructuras complejos, matrices, colecciones, listas, diccionarios, tipos generics, etc.
    • Por ejemplo, aunque la lista o el diccionario sean de solo lectura, eso no significa que el contenido sea.
  • Cómo lidiar con las clases heredadas
    • ¿De alguna manera base.GetHashCode() incorporar base.GetHashCode() en su código hash?
  • ¿Podrías técnicamente simplemente ser perezoso y regresar 0? Rompería en gran medida el número 3 de la guía de MSDN, pero al menos se aseguraría de que # 1 y # 2 siempre fueran verdaderos: P
  • Errores comunes y trampas.

¿Cuáles son esos números mágicos que a menudo se ven en las implementaciones de GetHashCode?

Ellos son números primos. Los números primos se usan para crear códigos hash porque el número primo maximiza el uso del espacio del código hash.

Específicamente, comience con el número primo pequeño 3, y considere solo los nybbles de bajo orden de los resultados:

  • 3 * 1 = 3 = 3 (mod 8) = 0011
  • 3 * 2 = 6 = 6 (mod 8) = 1010
  • 3 * 3 = 9 = 1 (mod 8) = 0001
  • 3 * 4 = 12 = 4 (mod 8) = 1000
  • 3 * 5 = 15 = 7 (mod 8) = 1111
  • 3 * 6 = 18 = 2 (mod 8) = 0010
  • 3 * 7 = 21 = 5 (mod 8) = 1001
  • 3 * 8 = 24 = 0 (mod 8) = 0000
  • 3 * 9 = 27 = 3 (mod 8) = 0011

Y comenzamos de nuevo. Pero notará que los múltiplos sucesivos de nuestro primo generan cada permutación posible de bits en nuestro nybble antes de comenzar a repetir. Podemos obtener el mismo efecto con cualquier número primo y cualquier número de bits, lo que hace que los números primos sean óptimos para generar códigos hash casi aleatorios. La razón por la que usualmente vemos primos más grandes en lugar de números primos pequeños como 3 en el ejemplo anterior es que, para un mayor número de bits en nuestro código hash, los resultados obtenidos al utilizar un primo pequeño ni siquiera son pseudoaleatorios; simplemente son un aumentando la secuencia hasta que se encuentre un desbordamiento. Para la aleatoriedad óptima, se debe usar un número primo que resulte en desbordamiento para coeficientes bastante pequeños, a menos que pueda garantizar que sus coeficientes no serán pequeños.

Enlaces relacionados:

  • ¿Por qué se usa ‘397’ para la anulación de ReSharper GetHashCode?

Consulte las pautas y reglas para GetHashCode por Eric Lippert

Debe anularlo siempre que tenga una medida significativa de igualdad para objetos de ese tipo (es decir, anula Iguales). Si supieras que el objeto no va a ser hash por algún motivo, puedes abandonarlo, pero es poco probable que puedas saberlo con antelación.

El hash debe basarse solo en las propiedades del objeto que se utilizan para definir la igualdad, ya que dos objetos que se consideran iguales deben tener el mismo código hash. En general, generalmente harías algo como:

 public override int GetHashCode() { int mc = //magic constant, usually some prime return mc * prop1.GetHashCode() * prop2.GetHashCode * ... * propN.GetHashCode(); } 

Por lo general, supongo que multiplicar los valores en conjunto producirá una distribución bastante uniforme, suponiendo que la función de código hash de cada propiedad hace lo mismo, aunque esto puede ser incorrecto. Usando este método, si las propiedades de definición de igualdad de los objetos cambian, entonces es probable que el código de hash también cambie, lo cual es aceptable dada la definición # 2 en su pregunta. También trata con todos los tipos de una manera uniforme.

Puede devolver el mismo valor para todas las instancias, aunque esto hará que cualquier algoritmo que use hashing (como dictionarys) sea muy lento; básicamente, todas las instancias se dividirán en hash en el mismo depósito y la búsqueda pasará a ser O (n) en lugar de la esperada O (1). Esto, por supuesto, niega los beneficios del uso de tales estructuras para la búsqueda.

¿Por qué tengo que anular object.GetHashCode() ?

Anular este método es importante porque la siguiente propiedad siempre debe permanecer verdadera:

Si dos objetos se comparan como iguales, el método GetHashCode para cada objeto debe devolver el mismo valor.

La razón, según lo declarado por JaredPar en una publicación de blog sobre la implementación de la igualdad, es que

Muchas clases usan el código hash para clasificar un objeto. En particular, las tablas hash y los diccionarios tienden a colocar objetos en cubos según su código hash. Al verificar si un objeto ya está en la tabla hash, primero lo buscará en un cubo. Si dos objetos son iguales pero tienen diferentes códigos hash, pueden colocarse en diferentes cubos y el diccionario no buscará el objeto.

Enlaces relacionados:

  • ¿TENGO que anular GetHashCode e Igual en las nuevas clases?
  • ¿Debo anular GetHashCode () en los tipos de referencia?
  • Ignorar Igual y Pregunta GetHashCode
  • ¿Por qué es importante anular GetHashCode cuando el método Equals es anulado en C #?
  • Implementación adecuada de la igualdad en VB

A) Debe anular tanto Equal como GetHashCode si desea emplear la igualdad de valor en lugar de la igualdad de referencia predeterminada. Con el último, dos referencias de objeto se comparan como iguales si ambas se refieren a la misma instancia de objeto. Con el primero se comparan como iguales si su valor es el mismo, incluso si se refieren a diferentes objetos. Por ejemplo, es probable que desee emplear igualdad de valor para los objetos Fecha, Dinero y Punto.

B) Para implementar igualdad de valores, debes anular Equals y GetHashCode. Ambos deberían depender de los campos del objeto que encapsulan el valor. Por ejemplo, Date.Year, Date.Month y Date.Day; o Money.Currency y Money.Amount; o Point.X, Point.Y y Point.Z. También debería considerar anular operador ==, operador! =, Operador .

C) El código hash no tiene que permanecer constante a lo largo de la vida del objeto. Sin embargo, debe permanecer inmutable mientras participa como la clave en un hash. Desde MSDN doco for Dictionary: “Siempre que un objeto se use como una clave en el Diccionario <(Of <(TKey, TValue>)>), no debe cambiar de ninguna manera que afecte su valor hash”. Si debe cambiar el valor de una clave, elimine la entrada del diccionario, cambie el valor de la clave y reemplace la entrada.

D) OMI, simplificará su vida si sus objetos de valor son inmutables.

¿Cuándo object.GetHashCode() ?

Como dice MSDN :

Los tipos que anulan Iguales también deben anular GetHashCode; de lo contrario, Hashtable podría no funcionar correctamente.

Enlaces relacionados:

  • Cuándo anular GetHashCode ()?

¿En qué campos basar el código hash? Si solo debe estar en campos inmutables, ¿qué pasa si solo hay campos mutables?

No necesita estar basado solo en campos inmutables. Lo basaría en los campos que determinan el resultado del método igual.

Cómo asegurarse de que el código hash permanezca igual durante toda la vida del objeto. (Propiedad de MSDN n. ° 2) Especialmente cuando la igualdad se basa en campos mutables. (Propiedad de MSDN n. ° 1)

Parece que malinterpretas la Propiedad # 2. El código hash no necesita permanecer igual a lo largo de la vida de los objetos. Simplemente debe permanecer igual siempre que los valores que determinan el resultado del método igual no se modifiquen. Entonces, lógicamente, basas el hashcode solo en esos valores. Entonces no debería haber un problema.

 public override int GetHashCode() { return IntProp1 ^ IntProp2 ^ StrProp3.GetHashCode() ^ StrProp4.GetHashCode ^ CustomClassProp.GetHashCode; } 

Haga lo mismo en el método GetHasCode de GetHasCode . Funciona de maravilla.