给定像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的作者并做出一个覆盖GetHashCode()
在Nullable<>
其结构是相关的,虽然)。无参数的用户编写的实现GetHashCode()
,其中,其哈希码我们所追求的目标是永远不能处理的情况null
。
这是关于实现抽象方法EqualityComparer<T>.GetHashCode(T)
或以其他方式实现接口方法IEqualityComparer<T>.GetHashCode(T)
。现在,当创建到MSDN的这些链接时,我看到它说如果这些方法ArgumentNullException
的唯一参数为,则它们会抛出一个null
。这肯定是MSDN上的错误吗?.NET自己的实现均未引发异常。在这种情况下抛出将有效地中断添加null
到的任何尝试HashSet<>
。除非HashSet<>
在处理null
项目时做一些特别的事情(我将不得不对此进行测试)。
新编辑/添加:
现在我尝试调试。使用HashSet<>
,我可以确认使用默认的相等比较器,这些值Season.Spring
和null
将在同一存储桶中结束。这可以通过非常仔细地检查私有数组成员m_buckets
和来确定m_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;
}
}