考虑到像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?
)只能接受5个值,但是其中的两个,即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
有什么原因吗?
EDIT/ADDITION:个
一些人似乎认为这是关于超越Object.GetHashCode()
.事实上,它真的不是.(不过,.NET的作者确实在Nullable<>
struct 中重写了GetHashCode()
,这与is相关.)无参数GetHashCode()
的用户编写的实现永远不能处理我们寻找的散列代码为null
的对象的情况.
这是关于实现抽象方法EqualityComparer<T>.GetHashCode(T)
或以其他方式实现接口方法IEqualityComparer<T>.GetHashCode(T)
.现在,在创建这些到MSDN的链接时,我看到那里写着,如果这些方法的唯一参数是null
,则抛出ArgumentNullException
.这肯定是MSDN上的一个错误?.NET自己的实现都没有抛出异常.投入这种情况将有效地打破任何试图在HashSet<>
的基础上增加null
的企图.除非HashSet<>
在处理null
项时做了一些非同寻常的事情(我将不得不测试这一点).
NEW EDIT/ADDITION:
现在我试着调试.对于HashSet<>
,我可以确认使用默认的相等比较器,值Season.Spring
和null
will在同一个桶中结束.这可以通过非常仔细地判断私有数组成员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;
}
这意味着,at least for 100, it is not even possible to change the hash of 101.相反,解决方案是更改所有其他值的散列,如下所示:
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;
}
}