鉴于像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 的链接时,我看到它在那里说这些方法会抛出一个ArgumentNullExceptionif 它们的唯一参数是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;
}
}