Si usa el propio menú de refactorización de Visual Studio para agregar una implementación GetHashCode a una clase como esta:
y seleccione la única propiedad int de la clase:
genera este código en .NET Framework:
public override int GetHashCode()
{
return -1937169414 + Value.GetHashCode();
}
( HashCode.Combine(Value)
en su lugar, genera en .NET Core, que no estoy seguro si implica el mismo valor)
¿Qué tiene de especial este valor? ¿Por qué Visual Studio no usa Value.GetHashCode()
directamente? Según tengo entendido, realmente no afecta la distribución de hash. Como es solo una suma, los valores consecutivos aún se acumularían juntos.
EDITAR: solo intenté esto con diferentes clases con Value
propiedades, pero aparentemente el nombre de la propiedad afecta el número generado. Por ejemplo, si cambia el nombre de la propiedad a Halue
, el número se convierte en 387336856. Gracias a Gökhan Kurt que señaló esto.
fuente
int
.-1937169414
es la multiplicación entera de-1521134295
y-783812246
. El número más significativo aquí es el-1521134295
que aparece en cada cálculo de código hash.-783812246
es el número de semilla Se elige un número de semilla en función del número de miembros en la ecuación. En las clases anónimas, el número de semilla se calcula en función de los nombres de campo. Por lo tanto, hay tantos números de semillas como números enteros. Podemos suponer que un número de semilla es aleatorio. En cuanto a la importancia de-1521134295
, creo que reduce la colisión y solo un desarrollador interno podría responder con precisión cómo.Respuestas:
Si busca
-1521134295
en los repositorios de Microsoft, verá que aparece varias vecesLa mayoría de los resultados de búsqueda están en las
GetHashCode
funciones, pero todos tienen el siguiente formularioEl primero
hashCode * -1521134295 = SOME_CONSTANT * -1521134295
se multiplicará previamente durante el tiempo de generación por el generador o durante el tiempo de compilación por CSC. Esa es la razón-1937169414
en tu códigoProfundizar en los resultados revela la parte de generación de código que se puede encontrar en la función CreateGetHashCodeMethodStatements
Como puede ver, el hash depende de los nombres de los símbolos. En esa función también se llama la constante
permuteValue
, probablemente porque después de la multiplicación los bits se permutan de alguna maneraHay algunos patrones si vemos el valor en binario:
101001 010101010101010 101001 01001
o10100 1010101010101010 10100 10100 1
. Pero si multiplicamos un valor arbitrario con eso, entonces hay muchos traslapes superpuestos, por lo que no podría ver cómo funciona. La salida también puede tener un número diferente de bits establecidos, por lo que no es realmente una permutaciónPuede encontrar otro generador en AnonymousTypeGetHashCodeMethodSymbol de Roslyn que llama a la constante
HASH_FACTOR
La verdadera razón para elegir ese valor aún no está clara
fuente
Como GökhanKurt explicó en los comentarios, el número cambia según los nombres de propiedad involucrados. Si cambia el nombre de la propiedad a
Halue
, el número se convierte en 387336856 en su lugar. Lo había intentado con diferentes clases pero no pensé en renombrar la propiedad.El comentario de Gökhan me hizo comprender su propósito. Está compensando valores hash basados en un desplazamiento determinista, pero distribuido aleatoriamente. De esta forma, la combinación de valores hash para diferentes clases, incluso con una simple adición, sigue siendo ligeramente resistente a las colisiones hash.
Por ejemplo, si tiene dos clases con implementaciones de GetHashCode similares:
y si tienes otra clase que contiene referencias a estos dos:
una combinación pobre como esta sería propensa a colisiones hash porque el código hash resultante se acumularía alrededor de la misma área para diferentes valores de ValueA y ValueB si sus valores están cerca uno del otro. Realmente no importa si usa la multiplicación o las operaciones bit a bit para combinarlas, seguirían siendo propensas a colisiones sin un desplazamiento uniformemente distanciado. Como muchos valores enteros utilizados en la programación se acumulan alrededor de 0, tiene sentido utilizar dicho desplazamiento
Aparentemente, es una buena práctica tener un desplazamiento aleatorio con buenos patrones de bits.
Todavía no estoy seguro de por qué no usan compensaciones completamente aleatorias, probablemente para no romper ningún código que se base en el determinismo de GetHashCode (), pero sería genial recibir un comentario del equipo de Visual Studio sobre esto.
fuente