Primordial GetHashCode para objetos mutables?


He leído alrededor de 10 preguntas diferentes sobre cuándo y cómo anular GetHashCode pero todavía hay algo que no entiendo del todo. La mayoría de las implementaciones de GetHashCode se basan en los códigos hash de los campos del objeto, pero se ha citado que el valor de GetHashCode nunca debe cambiar durante la vida útil del objeto. ¿Cómo funciona si los campos en los que se basa son mutables? Además, ¿qué pasa si quiero que las búsquedas de diccionario, etc. se basen en la igualdad de referencia y no en mi Equals anulado?

Estoy principalmente sobreescribiendo Equals para la facilidad de probar unitariamente mi código de serialización, que asumo que serializar y deserializar (a XML en mi caso) mata la igualdad de referencia, por lo que quiero asegurarme de que al menos sea correcta por igualdad de valor. ¿Es esta mala práctica anular Equals en este caso? Básicamente en la mayor parte del código de ejecución quiero igualdad de referencia y siempre uso == y no estoy anulando eso. Debería simplemente crear un nuevo método ValueEquals o algo en lugar de overriding Equals? Solía asumir que el framework siempre usa == y no Equals para comparar cosas y así pensé que era seguro anular Equals ya que me parecía que su propósito era para si quieres tener una 2da definición de igualdad que es diferente del operador ==. De la lectura de varias otras preguntas, aunque parece que no es el caso.

EDITAR:

Parece que mis intenciones no estaban claras, lo que quiero decir es que el 99% de las veces igualdad de referencia, comportamiento predeterminado, sin sorpresas. Para casos muy raros quiero tener igualdad de valor, y quiero solicitar explícitamente igualdad de valor usando .Equals en lugar de ==.

Cuando hago esto el compilador recomienda que anule GetHashCode también, y así es como surgió esta pregunta. Parecía que hay objetivos contradictorios para GetHashCode cuando se aplica a objetos mutables, siendo estos:

  1. Si a.Equals(b) entonces a.GetHashCode() debería == b.GetHashCode().
  2. El valor de a.GetHashCode() nunca debe cambiar durante la vida de a.

Estos parecen contradecir naturalmente cuando un objeto mutable, porque si el estado del objeto cambia, esperamos que el valor de .Equals() cambie, lo que significa que GetHashCode debe cambiar para coincidir con el cambio en .Equals(), pero GetHashCode no debe cambiar.

¿Por qué parece haber esta contradicción? ¿No se pretende que estas recomendaciones se apliquen a objetos mutables? Probablemente asumido, pero podría valer la pena mencionar que me refiero a clases no estructuras.

Resolución:

Estoy marcando JaredPar como aceptado, pero principalmente para la interacción de comentarios. Para resumir lo que he aprendido de esto es que la única manera de lograr todos los objetivos y evitar posibles comportamientos extravagantes en casos extremos es anular Equals y GetHashCode basándose en campos inmutables, o implementar IEquatable. Este tipo de parece disminuir la utilidad de anular Equals para los tipos de referencia, como por lo que he visto la mayoría de los tipos de referencia por lo general no tienen campos inmutables a menos que se almacenen en una base de datos relacional para identificarlos con sus claves primarias.

Author: Wai Ha Lee, 2009-05-17

5 answers

¿Cómo funciona eso si los campos en los que se basa son mutables?

No lo hace en el sentido de que el código hash cambiará a medida que cambie el objeto. Eso es un problema por todas las razones enumeradas en los artículos que lee. Desafortunadamente, este es el tipo de problema que normalmente solo aparece en casos de esquina. Así que los desarrolladores tienden a salirse con la suya con el mal comportamiento.

También lo que si quiero búsquedas de diccionario, etc que se basan en la igualdad de referencia no mi invalidado ¿Iguales?

Mientras implemente una interfaz como IEquatable<T> esto no debería ser un problema. La mayoría de las implementaciones de diccionario elegirán un comparador de igualdad de una manera que usará IEquatable<T> sobre Object.ReferenceEquals. Incluso sin IEquatable<T>, la mayoría llamará por defecto a Object.Equals () que luego irá a tu implementación.

Básicamente en la mayor parte del código de ejecución quiero igualdad de referencia y siempre uso == y no estoy anulando eso.

Si esperas que tus objetos para comportarse con igualdad de valor debe anular = = y != para imponer la igualdad de valor para todas las comparaciones. Los usuarios aún pueden usar Object.ReferenceEquals si realmente quieren igualdad de referencia.

Solía asumir que el framework siempre usa = = y no es Igual para comparar cosas

Lo que usa la BCL ha cambiado un poco con el tiempo. Ahora la mayoría de los casos que usan igualdad tomarán una instancia IEqualityComparer<T> y la usarán para igualdad. En los casos en que no se especifique uno se utilizará EqualityComparer<T>.Default para encontrar uno. En el peor de los casos, por defecto llamará a Object.Es igual a

 20
Author: JaredPar,
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-05-17 01:07:07

Si tiene un objeto mutable, no tiene mucho sentido reemplazar el método GetHashCode, ya que realmente no puede usarlo. Se utiliza, por ejemplo, en las colecciones Dictionary y HashSet para colocar cada elemento en un cubo. Si cambia el objeto mientras se usa como clave en la colección, el código hash ya no coincide con el bucket en el que se encuentra el objeto, por lo que la colección no funciona correctamente y es posible que nunca vuelva a encontrar el objeto.

Si desea que la búsqueda no utilice el GetHashCode o Equals método de la clase, siempre puede proporcionar su propia implementación IEqualityComparer para usar en su lugar cuando cree el Dictionary.

El método Equals está destinado a la igualdad de valores, por lo que no está mal implementarlo de esa manera.

 6
Author: Guffa,
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-05-17 01:21:55

Wow, eso es en realidad varias preguntas en una : -). Así que uno tras otro:

Se ha citado que el valor de GetHashCode nunca debe cambiar durante la vida útil del objeto. ¿Cómo funciona si los campos en los que se basa son mutables?

Este consejo común está destinado para el caso en el que desea usar su objeto como clave en una tabla HASH/diccionario, etc. . Los HashTables generalmente requieren que el hash no cambie, porque lo usan para decidir cómo almacenar & recupera la llave. Si el hash cambia, la tabla hash probablemente ya no encontrará su objeto.

Para citar los documentos de la interfaz de Java Map:

Nota: se debe tener mucho cuidado si se utilizan objetos mutables como claves de mapa. El comportamiento de un mapa no se especifica si el valor de un objeto se cambia de una manera que afecta a las comparaciones iguales mientras que el objeto es una clave en el mapa.

En general, es una mala idea usar cualquier tipo de objeto mutable como clave en una tabla hash: Ni siquiera está claro qué debería suceder si una clave cambia después de haber sido agregada a la tabla hash. ¿La tabla hash debe devolver el objeto almacenado a través de la clave antigua, o a través de la nueva clave, o a través de ambas?

Así que el consejo real es: Solo use objetos inmutables como claves, y asegúrese de que su hashcode tampoco cambie (lo que generalmente es automático si el objeto es inmutable).

También qué pasa si quiero que las búsquedas de diccionario, etc. se basen en la igualdad de referencia ¿no mis Iguales anulados?

Bueno, encuentra una implementación de diccionario que funcione así. Pero los diccionarios de la biblioteca estándar usan el hashcode & Equals, y no hay manera de cambiar eso.

Estoy sobrescribiendo principalmente Equals para la facilidad de probar unitariamente mi código de serialización, que asumo que serializar y deserializar (a XML en mi caso) mata la igualdad de referencia, por lo que quiero asegurarme de que al menos sea correcta por igualdad de valor. Es esta mala práctica para reemplazar ¿Iguales en este caso?

No, lo encontraría perfectamente aceptable. Sin embargo, no debe usar tales objetos como claves en un diccionario/hashtable, ya que son mutables. Véase supra.
 3
Author: sleske,
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-05-17 12:25:24

El tema subyacente aquí es cómo identificar objetos de manera única. Usted menciona la serialización / deserialización que es importante porque la integridad referencial se pierde en ese proceso.

La respuesta corta, es que los objetos deben ser identificados de manera única por el conjunto más pequeño de campos inmutables que se pueden usar para hacerlo. Estos son los campos que debe usar al sobrescribir GetHashCode e Equals.

Para probar es perfectamente razonable definir cualquier aserción que necesidad, por lo general estos no se definen en el tipo en sí, sino más bien como métodos de utilidad en el conjunto de pruebas. Tal vez un TestSuite.assertEquals (MyClass, MyClass) ?

Tenga en cuenta que GetHashCode e Equals deberían trabajar juntos. GetHashCode debe devolver el mismo valor para dos objetos si son iguales. Equals debe devolver true si y solo si dos objetos tienen el mismo código hash. (Tenga en cuenta que es posible que dos objetos no sean iguales, pero pueden devolver el mismo código hash). Hay un montón de página web que aborda este tema de frente, solo google distancia.

 1
Author: Joe,
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-05-17 01:35:10

Yo no sé acerca de C#, ser pariente de noob, pero en Java, si reemplazar equals() también es necesario reemplazar hashCode (), para mantener el contrato entre ellos (y viceversa)... Y Java también tiene el mismo catch 22; básicamente te obliga a usar campos inmutables... Pero esto es un problema solo para las clases que se utilizan como una clave hash, y Java tiene implementaciones alternativas para todas las colecciones basadas en hash... que tal vez no tan rápido, pero efectivamente le permiten utilizar un objeto mutable como llave... es solo (por lo general) fruncido el ceño como un "diseño pobre".

Y siento la necesidad de señalar que este problema fundamental es atemporal... Ha existido desde que Adam era un muchacho.

He trabajado en el código fortran que es más antiguo que yo (tengo 36 años) que se rompe cuando se cambia un nombre de usuario (como cuando una chica se casa o se divorcia ;-) ... Así es la ingeniería, La solución adoptada fue: El "método" GetHashCode recuerda el hashCode previamente calculado, recalcula el hashCode (es decir, un marcador IsDirty virtual) y si los campos de claves han cambiado devuelve null. Esto hace que la caché elimine al usuario "sucio" (llamando a otro GetPreviousHashCode) y luego la caché devuelve null, haciendo que el usuario vuelva a leer de la base de datos. Un truco interesante y que vale la pena; incluso si lo digo yo mismo; -)

Compensaré la mutabilidad (solo deseable en casos de esquina) por el acceso O(1) (deseable en todos los casos). Bienvenido a la ingeniería; la tierra de los informados compromiso.

Salud. Keith.
 1
Author: corlettk,
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-05-17 06:57:17