¿Cómo funciona la implementación predeterminada para el GetHashCode()
trabajo? ¿Y maneja estructuras, clases, matrices, etc. de manera eficiente y lo suficientemente bien?
Estoy tratando de decidir en qué casos debo empacar el mío y en qué casos puedo confiar de manera segura en que la implementación predeterminada funcione bien. No quiero reinventar la rueda, si es posible.
.net
hash
gethashcode
Fung
fuente
fuente
GetHashCode()
se ha anulado) usandoSystem.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj)
Respuestas:
InternalGetHashCode se asigna a una función ObjectNative :: GetHashCode en el CLR, que se ve así:
La implementación completa de GetHashCodeEx es bastante grande, por lo que es más fácil simplemente vincular al código fuente de C ++ .
fuente
string
anulaGetHashCode
. Por otro lado, suponga que desea llevar un recuento de cuántas veces varios controles procesanPaint
eventos. Puede usar unDictionary<Object, int[]>
(cadaint[]
almacenado contendría exactamente un elemento).Para una clase, los valores predeterminados son esencialmente igualdad de referencia, y eso generalmente está bien. Si escribe una estructura, es más común anular la igualdad (no menos importante para evitar el boxeo), ¡pero es muy raro que escriba una estructura de todos modos!
Al anular la igualdad, siempre debe tener una coincidencia
Equals()
yGetHashCode()
(es decir, para dos valores, siEquals()
devuelve verdadero, deben devolver el mismo código hash, pero no es necesario lo inverso ), y es común también proporcionar==
/!=
operadores, y a menudo implementarIEquatable<T>
también.Para generar el código hash, es común usar una suma factorizada, ya que esto evita colisiones en valores emparejados, por ejemplo, para un hash básico de 2 campos:
Esto tiene la ventaja de que:
etc., que puede ser común si solo usa una suma no ponderada, o xor (
^
), etc.fuente
unchecked
. Afortunadamente,unchecked
es el valor predeterminado en C #, pero sería mejor hacerlo explícito; editadoLa documentación del
GetHashCode
método para Object dice que "la implementación predeterminada de este método no debe usarse como un identificador de objeto único para propósitos de hash". y el de ValueType dice "Si llama al método GetHashCode del tipo derivado, es probable que el valor de retorno no sea adecuado para usarlo como clave en una tabla hash". .Los tipos de datos básicos como
byte
,short
,int
,long
,char
ystring
poner en práctica un método GetHashCode buena. Algunas otras clases y estructuras, comoPoint
por ejemplo, implementan unGetHashCode
método que puede o no ser adecuado para sus necesidades específicas. Solo tienes que probarlo para ver si es lo suficientemente bueno.La documentación para cada clase o estructura puede decirle si anula la implementación predeterminada o no. Si no lo anula, debe usar su propia implementación. Para cualquier clase o estructura que cree usted mismo donde necesite usar el
GetHashCode
método, debe hacer su propia implementación que use los miembros apropiados para calcular el código hash.fuente
Como no pude encontrar una respuesta que explique por qué deberíamos anular
GetHashCode
yEquals
para estructuras personalizadas y por qué la implementación predeterminada "no es probable que sea adecuada para usar como clave en una tabla hash", dejaré un enlace a este blog post , que explica por qué con un ejemplo de caso real de un problema que sucedió.Recomiendo leer la publicación completa, pero aquí hay un resumen (énfasis y aclaraciones añadidas).
Razón por la cual el hash predeterminado para estructuras es lento y no muy bueno:
Problema del mundo real descrito en la publicación:
Por lo tanto, para responder la pregunta "en qué casos debo empacar el mío y en qué casos puedo confiar con seguridad en la implementación predeterminada", al menos en el caso de las estructuras , debe anular
Equals
yGetHashCode
cada vez que su estructura personalizada se pueda usar como clave en una tabla hash oDictionary
.También recomendaría implementar
IEquatable<T>
en este caso, para evitar el boxeo.Como dicen las otras respuestas, si está escribiendo una clase , el hash predeterminado que usa la igualdad de referencia generalmente está bien, por lo que no me molestaría en este caso, a menos que necesite anular
Equals
(entonces tendría que anular enGetHashCode
consecuencia).fuente
En términos generales, si anula Iguales, desea anular GetHashCode. La razón de esto es porque ambos se usan para comparar la igualdad de su clase / estructura.
Igual se utiliza cuando se verifica Foo A, B;
si (A == B)
Como sabemos que no es probable que el puntero coincida, podemos comparar los miembros internos.
GetHashCode generalmente es utilizado por tablas hash. El código hash generado por su clase siempre debe ser el mismo para un estado de entrega de clases.
Normalmente lo hago,
Algunos dirán que el código hash solo debe calcularse una vez por vida útil del objeto, pero no estoy de acuerdo con eso (y probablemente estoy equivocado).
Usando la implementación predeterminada proporcionada por el objeto, a menos que tenga la misma referencia a una de sus clases, no serán iguales entre sí. Al anular Equals y GetHashCode, puede informar la igualdad basada en valores internos en lugar de la referencia de objetos.
fuente
Si solo está tratando con POCO, puede usar esta utilidad para simplificar un poco su vida:
...
fuente