考虑到像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个值,但是其中的两个,即nullSeason.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.Springnull will在同一个桶中结束.这可以通过非常仔细地判断私有数组成员m_bucketsm_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;
  }
}

推荐答案

只要为null返回的哈希代码对于该类型是consistent,就可以了.哈希代码的唯一要求是,被认为相等的两个对象共享相同的哈希代码.

返回0或-1表示空,只要您 Select 一个并始终返回它,就可以工作.显然,非NULL散列代码不应该返回您为NULL使用的任何值.

Similar questions:

GetHashCode on null fields?

What should GetHashCode return when object's identifier is null?

MSDN entry条中的"备注"将围绕哈希代码进行更详细的介绍.令人痛心的是,该文档没有提供任何关于空值at all的报道或讨论,甚至在社区内容中也没有.

要解决枚举的问题,可以重新实现哈希代码以返回非零,添加一个默认的"未知"枚举条目,相当于null,或者干脆不使用可为null的枚举.

顺便说一句,有趣的发现.

我通常看到的另一个问题是,散列码cannot代表一个4字节或更大的类型,在没有at least one collision的情况下可以为空(随着类型大小的增加而增加).例如,int的哈希代码就是int,所以它使用完整的int范围.为null Select 该范围内的哪个值?无论您 Select 哪一个,都会与值的哈希代码本身发生冲突.

碰撞本身并不一定是个问题,但你需要知道它们确实存在.哈希代码仅在某些情况下使用.正如MSDN上的文档所述,哈希代码不能保证为不同的对象返回不同的值,因此不应期望返回不同的值.

.net相关问答推荐

";Make Async ValueTask/ValueTask方法分期分配发生了什么?

在数据网格中:如何在更改单元格 A 中的值后显示单元格 B 中的更改

部署时如何控制红隼端口?

查找所有源硬编码字符串

在 .NET 中获取执行 exe 路径的最佳方法是什么?

如何以编程方式 Select ListView 中的项目?

如何将自定义 UserControl 显示为对话框?

.net:System.Web.Mail 与 System.Net.Mail

[DllImport("QCall")] 是什么?

为什么 WCF 中不允许方法重载?

操作对事务的状态无效错误和事务范围

ObservableCollection<> 与 List<>

形成两个列表并集的最简单方法

ASP.NET Core (.NET Core) 和 ASP.NET Core (.NET Framework) 的区别

如何在 C# 中以编程方式安装 Windows 服务?

ReaderWriterLockSlim 什么时候比简单的锁更好?

.Net 中 AOP 的最佳实现是什么?

如果锁定的对象内部发生异常,它会保持锁定状态吗?

ADO.NET Entity Framework:更新向导不会添加表

WPF 中的 Application.DoEvents() 在哪里?