Dado que as coleções System.Collections.Generic.HashSet<>aceitam nullcomo um membro do conjunto, pode-se perguntar qual nulldeveria ser o código hash . Parece que a estrutura 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
Isso pode ser (um pouco) problemático com enums anuláveis. Se definirmos
enum Season
{
Spring,
Summer,
Autumn,
Winter,
}
então, o Nullable<Season>(também chamado Season?) pode assumir apenas cinco valores, mas dois deles, a saber nulle Season.Spring, têm o mesmo código hash.
É tentador escrever um comparador de igualdade "melhor" 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;
}
}
Mas há alguma razão pela qual o código hash de nulldeveria ser 0?
EDITAR / ADICIONAR:
Algumas pessoas parecem pensar que se trata de substituir Object.GetHashCode(). Realmente não é, na verdade. (Os autores do .NET fizeram uma substituição de GetHashCode()na Nullable<>estrutura que é relevante, no entanto.) Uma implementação escrita pelo usuário do sem parâmetros GetHashCode()nunca pode lidar com a situação onde está o objeto cujo código hash procuramos null.
Trata-se de implementar o método abstrato EqualityComparer<T>.GetHashCode(T)ou de outra forma implementar o método de interface IEqualityComparer<T>.GetHashCode(T). Agora, ao criar esses links para o MSDN, vejo que diz ali que esses métodos lançam um ArgumentNullExceptionse seu único argumento for null. Isso certamente deve ser um erro no MSDN? Nenhuma das próprias implementações do .NET lança exceções. Nesse caso, jogar quebraria efetivamente qualquer tentativa de adicionar nulla HashSet<>. A menos que HashSet<>faça algo extraordinário ao lidar com um nullitem (terei que testar isso).
NOVA EDIÇÃO / ADIÇÃO:
Agora tentei depurar. Com HashSet<>, posso confirmar que, com o comparador de igualdade padrão, os valores Season.Springe null vai terminar no mesmo balde. Isso pode ser determinado inspecionando com muito cuidado os membros da matriz privada m_bucketse m_slots. Observe que os índices são sempre, por design, compensados por um.
O código que dei acima não corrige isso. Como se constatou, HashSet<>nunca perguntará ao comparador de igualdade quando o valor é null. Este é o código-fonte 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;
}
Isso significa que, pelo menos para HashSet<>, nem mesmo é possível alterar o hash de null. Em vez disso, uma solução é alterar o hash de todos os outros 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;
}
}