假设像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)
或以其他方式实现接口(interface)方法 IEqualityComparer<T>.GetHashCode(T)
。现在,在创建到MSDN的这些链接时,我看到它说如果这些方法的唯一参数是ArgumentNullException
,则它们将抛出null
。这肯定是MSDN上的错误吗? .NET自己的实现均未引发异常。在那种情况下抛出将有效地阻止将null
添加到HashSet<>
的任何尝试。除非HashSet<>
在处理null
项时做得非常出色(我将对此进行测试)。新编辑/添加:
现在我尝试调试。使用
HashSet<>
,我可以确认使用默认的相等比较器,值Season.Spring
和null
将结尾在同一存储桶中。这可以通过非常仔细地检查私有(private)数组成员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;
}
}
最佳答案
只要为空返回的哈希码与该类型一致,就可以了。哈希码的唯一要求是,被认为相等的两个对象共享相同的哈希码。
返回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上的文档所述,不保证哈希码会为不同的对象返回不同的值,因此不应期望这样做。