c# .net hash null

override int gethashcode c#



Si el código hash de null siempre fuera cero, en.NET (8)

¿Pero hay alguna razón por la cual el código hash de null debería ser 0?

Pudo haber sido cualquier cosa. Tiendo a estar de acuerdo en que 0 no fue necesariamente la mejor opción, pero es una que probablemente conduce a la menor cantidad de errores.

Una función hash absolutamente debe devolver el mismo hash para el mismo valor. Una vez que existe un componente que hace esto, este es realmente el único valor válido para el hash de null . Si hubiera una constante para esto, como, hm, object.HashOfNull , alguien que implementara un IEqualityComparer tendría que saber usar ese valor. Si no lo piensan, la probabilidad de que usen 0 es ligeramente mayor que cualquier otro valor, creo.

al menos para HashSet <>, ni siquiera es posible cambiar el hash de null

Como mencioné anteriormente, creo que es completamente imposible, porque existen tipos que ya siguen la convención de que el hash de null es 0.

Dado que las colecciones como System.Collections.Generic.HashSet<> aceptan null como un miembro del conjunto, uno puede preguntar cuál debería ser el código hash de null . Parece que el framework usa 0 :

// nullable struct type int? i = null; i.GetHashCode(); // gives 0 EqualityComparer<int?>.Default.GetHashCode(i); // gives 0 // class type CultureInfo c = null; EqualityComparer<CultureInfo>.Default.GetHashCode(c); // gives 0

Esto puede ser (un poco) problemático con enumeraciones que aceptan nulos. Si definimos

enum Season { Spring, Summer, Autumn, Winter, }

luego, el Nullable<Season> (también llamado Season? ) puede tomar solo cinco valores, pero dos de ellos, es decir, null y Season.Spring , tienen el mismo código hash.

Es tentador escribir un comparador de igualdad "mejor" como este:

class NewNullEnumEqComp<T> : EqualityComparer<T?> where T : struct { public override bool Equals(T? x, T? y) { return Default.Equals(x, y); } public override int GetHashCode(T? x) { return x.HasValue ? Default.GetHashCode(x) : -1; } }

¿Pero hay alguna razón por la cual el código hash de null debería ser 0 ?

EDITAR / ADICIÓN:

Algunas personas parecen pensar que se trata de anular Object.GetHashCode() . Realmente no es, en realidad. (Los autores de .NET hicieron una anulación de GetHashCode() en la estructura Nullable<> que es relevante, sin embargo). Una implementación escrita por el usuario de GetHashCode() parámetros nunca puede manejar la situación donde el objeto cuyo código de hash buscar es null .

Se trata de implementar el método abstracto EqualityComparer<T>.GetHashCode(T) o implementar el método de interfaz IEqualityComparer<T>.GetHashCode(T) . Ahora, al crear estos enlaces a MSDN, veo que dice que estos métodos arrojan una ArgumentNullException si su único argumento es null . Esto sin duda debe ser un error en MSDN? Ninguna de las implementaciones de .NET arroja excepciones. Al lanzar en ese caso se rompería efectivamente cualquier intento de agregar null a un HashSet<> . A menos que HashSet<> haga algo extraordinario cuando se trata de un elemento null (tendré que probar eso).

NUEVA EDICIÓN / ADICIÓN:

Ahora probé la depuración. Con HashSet<> , puedo confirmar que con el comparador de igualdad predeterminado, los valores Season.Spring y null terminarán en el mismo contenedor. Esto puede determinarse inspeccionando cuidadosamente los miembros de la matriz privada m_buckets y m_slots . Tenga en cuenta que los índices siempre están, por diseño, compensados ​​por uno.

El código que di más arriba, sin embargo, no soluciona esto. Como resultado, HashSet<> nunca preguntará al comparador de igualdad cuando el valor es null . Esto es del código fuente de HashSet<> :

// Workaround Comparers that throw ArgumentNullException for GetHashCode(null). private int InternalGetHashCode(T item) { if (item == null) { return 0; } return m_comparer.GetHashCode(item) & Lower31BitMask; }

Esto significa que, al menos para HashSet<> , ni siquiera es posible cambiar el hash de null . En cambio, una solución es cambiar el hash de todos los otros valores, como este:

class NewerNullEnumEqComp<T> : EqualityComparer<T?> where T : struct { public override bool Equals(T? x, T? y) { return Default.Equals(x, y); } public override int GetHashCode(T? x) { return x.HasValue ? 1 + Default.GetHashCode(x) : /* not seen by HashSet: */ 0; } }


Así que esto podría evitarse usando un valor enum Unknown (aunque parece un poco extraño que la Season sea ​​desconocida). Entonces algo como esto negaría este problema:

public enum Season { Unknown = 0, Spring, Summer, Autumn, Winter } Season some_season = Season.Unknown; int code = some_season.GetHashCode(); // 0 some_season = Season.Autumn; code = some_season.GetHashCode(); // 3

Entonces tendría valores únicos de código hash para cada temporada.


Buena pregunta.

Solo traté de codificar esto:

enum Season { Spring, Summer, Autumn, Winter, }

y ejecuta esto así:

Season? v = null; Console.WriteLine(v);

devuelve null

si lo hago, en cambio normal

Season? v = Season.Spring; Console.WriteLine((int)v);

devuelve 0 , como se esperaba, o simple Spring si evitamos el lanzamiento a int .

Entonces ... si haces lo siguiente:

Season? v = Season.Spring; Season? vnull = null; if(vnull == v) // never TRUE

EDITAR

Desde MSDN

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

En otras palabras: si dos objetos tienen el mismo código hash, eso no significa que sean iguales, porque la igualdad real está determinada por Iguales .

De MSDN nuevamente:

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.


Es 0 por simplicidad. No hay un requisito tan difícil. Solo necesita garantizar los requisitos generales de codificación hash.

Por ejemplo, debe asegurarse de que si dos objetos son iguales, sus códigos hash siempre deben ser iguales. Por lo tanto, los diferentes códigos hash siempre deben representar diferentes objetos (pero no es necesariamente verdadero viceversa: dos objetos diferentes pueden tener el mismo código hash, aunque si esto sucede a menudo, entonces esta no es una función hash de buena calidad, no tiene un buena resistencia a la colisión).

Por supuesto, restringí mi respuesta a los requisitos de la naturaleza matemática. También hay condiciones técnicas específicas de .NET, que puedes leer here . 0 para un valor nulo no está entre ellos.


Personalmente, encuentro que usar valores nulos es un poco incómodo y trato de evitarlos siempre que puedo. Tu problema es solo otra razón. A veces son muy útiles, pero mi regla de oro no es mezclar tipos de valores con valores nulos, si es posible, simplemente porque estos son de dos mundos diferentes. En .NET framework, parecen hacer lo mismo: muchos tipos de valores proporcionan el método TryParse , que es una forma de separar valores de ningún valor ( null ).

En su caso particular, es fácil deshacerse del problema porque maneja su propio tipo de Season .

(Season?)null para mí significa que "la temporada no está especificada", como cuando tienes un formulario web en el que no se requieren algunos campos. En mi opinión, es mejor especificar ese "valor" especial en la enum sí misma en lugar de usar un poco torpe Nullable<T> . Será más rápido (sin boxeo) más fácil de leer ( Season.NotSpecified vs null ) y resolverá su problema con los códigos hash.

Por supuesto, para otros tipos, como int no se puede expandir el dominio de valor y para denominar uno de los valores como especial no siempre es posible. Pero con int? la colisión del código hash es un problema mucho más pequeño, en todo caso.


Siempre que el código hash devuelto para nulls sea consistente para el tipo, debería estar bien. El único requisito para un código hash es que dos objetos que se consideran iguales comparten el mismo código hash.

Devolver 0 o -1 para null, siempre que elijas uno y lo devuelvas todo el tiempo, funcionará. Obviamente, los códigos hash no nulos no deben devolver el valor que use para null.

Preguntas similares:

GetHashCode en campos nulos?

¿Qué debería devolver GetHashCode cuando el identificador del objeto es nulo?

Las "Observaciones" de esta entrada de MSDN entran en más detalles en torno al código hash. Conmovedoramente, la documentación no proporciona ninguna cobertura o discusión de valores nulos en absoluto , ni siquiera en el contenido de la comunidad.

Para solucionar su problema con la enumeración, vuelva a implementar el código hash para devolver un valor distinto de cero, agregue una entrada de enum "desconocida" predeterminada equivalente a nulo, o simplemente no use enumeraciones que aceptan valores de nulos.

Un hallazgo interesante, por cierto.

Otro problema que veo con esto en general es que el código hash no puede representar un tipo de 4 bytes o más grande que sea anulable sin al menos una colisión (más a medida que aumente el tamaño del texto). Por ejemplo, el código hash de un int es solo el int, por lo que usa el rango entero completo. ¿Qué valor en ese rango eliges para null? Cualquiera que elija, colisionará con el código hash del valor en sí.

Las colisiones en sí mismas no son necesariamente un problema, pero necesita saber que están allí. Los códigos hash solo se usan en algunas circunstancias. Como se indica en los documentos en MSDN, no se garantiza que los códigos hash devuelvan valores diferentes para diferentes objetos, por lo que no debería esperarse.


Tenga en cuenta que el código hash se usa como primer paso para determinar la igualdad solamente, y [es / debería] nunca (ser) usado como una determinación de facto sobre si dos objetos son iguales.

Si los códigos hash de dos objetos no son iguales, se los trata como si no fueran iguales (porque suponemos que la implementación falsa es correcta, es decir, no hacemos una segunda adivinación). Si tienen el mismo código hash, entonces deben verificarse la igualdad real que, en su caso, el null y el valor enum fallarán.

Como resultado, usar cero es tan bueno como cualquier otro valor en el caso general.

Claro, habrá situaciones, como su enumeración, donde este cero se comparte con el código hash de un valor real . La pregunta es si, para usted, la sobrecarga minúscula de una comparación adicional causa problemas.

Si es así, defina su propio comparador para el caso de los que aceptan valores nulos para su tipo particular, y asegúrese de que un valor nulo siempre arroje un código hash que siempre es el mismo (¡por supuesto!) Y un valor que no puede ser obtenido por el subyacente el algoritmo de código hash propio del tipo. Para sus propios tipos, esto es factible. Para otros, buena suerte :)


No tiene por qué ser cero ; puede hacerlo 42 si lo desea.

Lo único que importa es la consistencia durante la ejecución del programa.

Es simplemente la representación más obvia, porque null menudo se representa como cero internamente. Lo que significa que, durante la depuración, si ve un código hash de cero, es posible que le pida que piense: "Hmm ... ¿fue este un problema de referencia nulo?"

Tenga en cuenta que si usa un número como 0xDEADBEEF , entonces alguien podría decir que está usando un número mágico ... y lo haría. (Se podría decir que cero también es un número mágico, y estarías en lo cierto ... excepto que es tan ampliamente usado como para ser una especie de excepción a la regla).