鉴于像 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自己的所有实现都不会抛出异常。在这种情况下抛出异常将有效地破坏任何尝试向HashSet<>
添加null
的尝试。除非HashSet<>
在处理null
项时做了一些特殊的处理(我将进行测试)。
新增编辑/补充:
现在我尝试进行调试。使用HashSet<>
,我可以确认在默认的相等比较器下,值Season.Spring
和null
将会位于同一个桶中。这可以通过仔细检查私有数组成员m_buckets
和m_slots
来确定。请注意,索引总是按设计偏移了一个。然而,我上面提供的代码并没有解决这个问题。事实证明,当值为
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;
}
这意味着,至少对于
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;
}
}
System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(null)
也会返回0
。 - Jeppe Stig Nielsen