鉴于像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()
(不过, .NET在相关null
了
这是关于实现抽象方法EqualityComparer<T>.GetHashCode(T)
或以其他方式实现接口方法IEqualityComparer<T>.GetHashCode(T)
。现在,在创建这些指向 MSDN 的链接时,我看到它在那里说这些方法会抛出一个ArgumentNullException
if 它们的唯一参数是null
. 这肯定是MSDN上的一个错误?.NET 自己的实现都不会引发异常。在这种情况下投掷将有效地破坏任何添加null
到HashSet<>
. 除非HashSet<>
在处理一个null
项目时做了一些特别的事情(我将不得不对此进行测试)。
新编辑/添加:
现在我尝试调试。使用HashSet<>
,我可以确认使用默认的相等比较器,值Season.Spring
和null
将在同一个存储桶中结束。这可以通过非常仔细地检查私有数组成员来确定m_buckets
和m_slots
. 请注意,根据设计,索引始终偏移 1。
然而,我上面给出的代码并没有解决这个问题。事实证明,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;
}
}