假设像 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?
)只能取五个值,但其中两个值,即 null
和 Season.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 的确在 Nullable<>
结构中覆盖了 GetHashCode()
,尽管这与 是相关。)无参数 GetHashCode()
的用户编写实现永远不能处理我们寻找的散列码是 null
的对象的情况。
这是关于实现抽象方法 EqualityComparer<T>.GetHashCode(T)
或以其他方式实现接口方法 IEqualityComparer<T>.GetHashCode(T)
。现在,在创建到 MSDN 的这些链接时,我看到它说,如果这些方法的唯一参数是 null
,那么它们将抛出一个 ArgumentNullException
。这肯定是 MSDN 上的一个错误吧?一个都没有。NET 自己的实现抛出异常。在这种情况下,抛出将有效地打破任何企图将 null
添加到 HashSet<>
。除非 HashSet<>
在处理 null
项目时做了一些特别的事情(我将不得不测试它)。
最新编辑/补充:
现在我尝试调试。使用 HashSet<>
,我可以确认使用默认的相等比较器,值 Season.Spring
和 null
威尔在同一个桶中结束。这可以通过非常仔细地检查私有数组成员 m_buckets
和 m_slots
来确定。注意,按照设计,索引总是被1抵消。
然而,我上面给出的代码并不能解决这个问题。事实证明,当值为 null
时,HashSet<>
甚至不会询问相等比较器。这是来自 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;
}
这意味着,至少对于 ABC0,甚至不可能改变 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;
}
}