Consejos generales y directrices sobre cómo anular correctamente un objeto.GetHashCode()


De acuerdo con 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 comparan 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 y cuando no haya modificación a el estado del objeto que determina el valor devuelto 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 de nuevo.

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


Sigo encontrándome en el siguiente escenario: He creado una clase, implementado IEquatable<T> y anulado 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 por lo general se detiene un poco para mí. Porque, ¿cómo se anula correctamente object.GetHashCode()? Nunca se sabe realmente por dónde empezar, y parece ser un montón de trampas.

Aquí en StackOverflow, hay bastantes preguntas relacionadas con la anulación de GetHashCode, pero la mayoría de ellas parece ser en casos bastante particulares y cuestiones específicas. Por lo tanto, me gustaría obtener una buena recopilación aquí. Una visión general con consejos y directrices generales. Qué hacer, qué no hacer, trampas comunes, por dónde empezar, etc.

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


Creo que tal vez la mejor manera es crear una respuesta por tema con una respuesta rápida y corta primero (cerca de una línea, si es que lo hace posible), a continuación, tal vez un poco más de información y terminar con preguntas relacionadas, discusiones, entradas de blog, etc. si hay alguna. 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 de blog. Trate de tomar la esencia de ellos y luego en lugar de enlace a la fuente (especialmente porque la fuente podría desaparecer. Además, intente editar y mejorar las respuestas en lugar de crear muchas muy similares.

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

Author: Svish, 2009-09-04

10 answers

Índice


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

  • Cómo crear el entero (Cómo "convertir" un objeto en un int no era muy obvio para mí de todos modos).
  • Qué campos a basar el código hash en.
    • Si solo debe estar en campos inmutables, ¿qué pasa si solo hay campos mutables?
  • Cómo generar una buena distribución aleatoria. (MSDN Propiedad # 3)
    • Parte de esto, parece elegir un buen número primo mágico (han visto 17, 23 y 397 se han utilizado), pero ¿cómo lo elige, y para qué es exactamente?
  • Cómo asegurarse de que el código hash permanezca igual durante toda la vida útil del objeto. (Propiedad de MSDN #2)
    • , Especialmente cuando la igualdad se basa en campos mutables. (MSDN Propiedad #1)
  • Cómo tratar con campos que son tipos complejos (no entre los tipos C# integrados ).
    • Objetos complejos y estructuras, arrays, colecciones, listas, diccionarios, tipos genéricos, etc.
    • Por ejemplo, aunque la lista o el diccionario puedan leerse solo, eso no significa que su contenido lo sea.
  • Cómo lidiar con la herencia clase.
    • ¿Debería incorporar de alguna manera base.GetHashCode() en su código hash?
  • ¿Podrías técnicamente ser perezoso y devolver 0? Rompería en gran medida la pauta de MSDN número # 3, pero al menos se aseguraría de que #1 y #2 siempre fueran ciertas: P
  • Trampas y trampas comunes.
 8
Author: Svish,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:45:36

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

Son números primos. Los números primos se utilizan para crear códigos hash porque los números primos maximizan el uso del espacio de código hash.

Específicamente, comience con el número primo pequeño 3, y considere solo el orden bajo nybbles 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 empezamos de nuevo. Pero te darás cuenta de que múltiplos sucesivos de nuestro primo genera cada permutación posible de bits en nuestro nybble antes de empezar 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 generalmente vemos primos más grandes en lugar de 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 usar un primo pequeño ni siquiera son pseudo-aleatorios, simplemente son una secuencia creciente hasta que se encuentra un desbordamiento. Para una aleatoriedad óptima, un número primo que resulta en desbordamiento para coeficientes bastante pequeños debe ser utilizado, a menos que pueda garantizar que sus coeficientes no serán pequeños.

Enlaces Relacionados:

 7
Author: Svish,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:25:03

Echa un vistazo a Directrices y reglas para GetHashCode por Eric Lippert

 3
Author: Ian Ringrose,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-06 16:23:11

Debe sobrescribirlo siempre que tenga una medida significativa de igualdad para objetos de ese tipo (es decir, sobrescribir Iguales). Si supieras que el objeto no iba a ser hash por cualquier razón, podrías dejarlo, pero es poco probable que lo sepas de antemano.

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, normalmente harías algo como:


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

Normalmente asumo que multiplicar los valores juntos producirá una distribución bastante uniforme, asumiendo que la función hashcode de cada propiedad hace lo mismo, aunque esto puede estar mal. Usando este método, si las propiedades que definen la igualdad de los objetos cambian, entonces el código hash también es probable que cambie, lo cual es aceptable dada la definición #2 en su pregunta. También se ocupa de todos los tipos de manera uniforme.

Puede devolver el mismo valor para todas las instancias, aunque esto hará que cualquier algoritmo que use hash (como dictionarys) sea muy lento, esencialmente todas las instancias serán hash en el mismo bucket y la búsqueda se convertirá en O(n) en lugar del esperado O(1). Esto, por supuesto, niega cualquier beneficio de usar tales estructuras para la búsqueda.

 2
Author: Lee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-09-04 11:45:37

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

Sobrescribir 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 un blog post 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 basados en su código hash. Al comprobar 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, se pueden poner en diferentes cubos y el diccionario fallaría al buscar el objeto.

Enlaces relacionados:

 2
Author: Svish,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:19:40

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

B) Con el fin de implementar la igualdad de valor que debe anular Equals y GetHashCode. Ambos deben depender de los campos del objeto que encapsulan el valor. Por ejemplo, Fecha.Año, Fecha.Mes y Fecha.Día; o Dinero.Moneda y Dinero.Cantidad; o Punto.X, Punto.Y y Punto.Z. También debe considerar sobreescribir operator==, operator != , operator .

C) El hashcode no tiene que permanecer constante durante toda la vida útil del objeto. Sin embargo, debe permanecer inmutable mientras participa como clave en un hash. De MSDN doco for Dictionary: "Mientras un objeto se utilice como clave en el Diccionario)>), no debe cambiar de ninguna manera que afecte a 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) IMO, simplificarás tu vida si tus objetos de valor son inmutables.

 2
Author: mount77,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-04 21:06:59

¿Cuándo anulo object.GetHashCode()?

Como MSDN dice:

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

Enlaces Relacionados:

 0
Author: Svish,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:19:40

¿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.

 0
Author: Matthijs Wessels,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-14 13:34:04

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

Usted parece malinterpretar la Propiedad #2. El hashcode no necesita permanecer igual durante toda la vida útil de los objetos. Solo tiene que permanecer igual, siempre y cuando los valores que determinan el resultado del método igual no se cambian. Así que lógicamente, basas el hashcode solo en esos valores. Entonces allí no debería ser un problema.

 0
Author: Matthijs Wessels,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-14 13:34:26
public override int GetHashCode()
{
    return IntProp1 ^ IntProp2 ^ StrProp3.GetHashCode() ^ StrProp4.GetHashCode ^ CustomClassProp.GetHashCode;
}

Haga lo mismo en el método GetHasCode de la clase personalizada. Funciona como un encanto.

 -2
Author: Burnsys,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-01 00:34:53