假设像System.Collections.Generic.HashSet<>这样的集合接受null作为集合成员,则可以询问null的哈希码应该是什么。看起来框架使用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

对于可为空的枚举,这可能会(有一点)问题。如果我们定义
enum Season
{
  Spring,
  Summer,
  Autumn,
  Winter,
}

那么Nullable<Season>(也称为Season?)只能接受五个值,但是其中两个值(即nullSeason.Spring)具有相同的哈希码。

编写这样的“更好”的相等比较器很诱人:
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;
  }
}

但是,是否有任何理由为什么null的哈希码应为0

编辑/添加:

有些人似乎认为这与重写Object.GetHashCode()有关。实际上,实际上并非如此。 (尽管.NET的作者确实在相关的GetHashCode()结构中重写了Nullable<>。)无参数GetHashCode()的用户编写实现永远无法处理我们要查找其哈希码为null的对象的情况。

这是关于实现抽象方法 EqualityComparer<T>.GetHashCode(T) 或以其他方式实现接口(interface)方法 IEqualityComparer<T>.GetHashCode(T) 。现在,在创建到MSDN的这些链接时,我看到它说如果这些方法的唯一参数是ArgumentNullException,则它们将抛出null。这肯定是MSDN上的错误吗? .NET自己的实现均未引发异常。在那种情况下抛出将有效地阻止将null添加到HashSet<>的任何尝试。除非HashSet<>在处理null项时做得非常出色(我将对此进行测试)。

新编辑/添加:

现在我尝试调试。使用HashSet<>,我可以确认使用默认的相等比较器,值Season.Springnull 结尾在同一存储桶中。这可以通过非常仔细地检查私有(private)数组成员m_bucketsm_slots来确定。请注意,根据设计,索引始终偏移一。

我上面给出的代码不能解决此问题。事实证明,当值是HashSet<>时,null甚至不会询问相等性比较器。这来自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;
    }

这意味着,至少对于HashSet<>来说,甚至不可能更改null的哈希值。 相反,一种解决方案是更改所有其他值的哈希,如下所示:
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;
  }
}

最佳答案

只要为空返回的哈希码与该类型一致,就可以了。哈希码的唯一要求是,被认为相等的两个对象共享相同的哈希码。

返回0或-1为null,只要您选择一个并一直返回就可以。显然,非null哈希码不应返回您用于null的任何值。

类似问题:

GetHashCode on null fields?

What should GetHashCode return when object's identifier is null?

MSDN entry的“备注”在哈希码周围有更详细的说明。可悲的是,该文档根本不提供或不讨论空值-甚至在社区内容中也没有提供。

要解决该枚举的问题,请重新实现哈希码以返回非零值,或者添加一个等效于null的默认“未知”枚举条目,或者干脆不使用可为空的枚举。

有趣的是,找到了。

我通常会看到的另一个问题是,哈希码不能表示一个4字节或更大的类型,如果没有至少有一个冲突(随着类型大小的增加),则该类型可以为空。例如,int的哈希码就是int,因此它使用完整的int范围。您选择该范围内的哪个值作为null?您选择的任何一种都会与值的哈希码本身发生冲突。

碰撞本身并不一定是问题,但是您需要知道它们在那里。哈希码仅在某些情况下使用。正如MSDN上的文档所述,不保证哈希码会为不同的对象返回不同的值,因此不应期望这样做。

08-28 08:27
查看更多