【问题标题】:How does one retrieve the hash code of an enumeration without boxing it?如何在不装箱的情况下检索枚举的哈希码?
【发布时间】:2012-12-13 08:28:24
【问题描述】:

如果有一个枚举存储在聚合类型中,则可能希望将其包含在该类型的哈希码中(假设一个典型的“multiply by primes”哈希函数)。如果只是调用SomeEnum.GetHashCode(),那么 JIT 似乎将实例装箱,即使在发布版本中也是如此。

对此进行分析表明,我的应用程序约有 10% 的时间用于在各种 GetHashCode 函数中进行装箱枚举。

多个值类型实现IEquatable或类似接口,允许以静态方法调用GetHashCode;这避免了拳击。但是System.Enum 不提供GetHashCode 的静态重载。是否有一些方法可以计算应该使用但避免装箱的代码?

【问题讨论】:

  • 何必呢?枚举是它自己的哈希码。只需转换为 int 并收工。
  • @Raymond:我认为这可能会导致分布不佳;但再考虑一下,我会看看它是否有效。
  • @Raymond:这确实适用于这个特定的测试用例。不过我要暂时打开这个......
  • 假设枚举的成员少于 40 亿,转换为 int 是一个完美的哈希(没有冲突)。
  • @RaymondChen 不一定。例如,考虑enum E : long { V1 = 1, V2 = 0x100000001L }

标签: c# hash clr hashtable


【解决方案1】:

您可以强制转换为枚举的基础类型(通常是 int,除非枚举定义另有说明)并使用该类型的覆盖 GetHashCode() 方法。

enum TestEnum
{
    Test1,
    Test2
}

TestEnum t = TestEnum.Test1;
((int)t).GetHashCode(); // no boxing
t.GetHashCode(); // boxing

这是此代码的 IL:

IL_0000:  nop
IL_0001:  ldc.i4.0
IL_0002:  stloc.0
IL_0003:  ldloc.0
IL_0004:  stloc.1
IL_0005:  ldloca.s   V_1
IL_0007:  call       instance int32 [mscorlib]System.Int32::GetHashCode()
IL_000c:  pop
IL_000d:  ldloc.0
IL_000e:  box        ConsoleApplication1.Program/TestEnum
IL_0013:  callvirt   instance int32 [mscorlib]System.Object::GetHashCode()
IL_0018:  pop
IL_0019:  ret

编辑:为了完整起见,我应该指出int.GetHashCode() 的主体只是return this;,正如Raymond Chen 在上面的评论中指出的那样,只需将枚举转换为@ 987654328@ 足以获得哈希码。

【讨论】:

  • 这实际上避免了拳击吗? (例如,int.GetHashCode() 不也会导致拳击吗?)
  • 是的,这避免了拳击。从枚举转换为 int 避免了装箱(参考 Jon Skeet 的第一条评论:bytes.com/topic/c-sharp/answers/…),并且在 int 上调用 GetHashCode 也不会导致装箱,因为 GetHashCode 被此结构覆盖(我什至反编译了它的实现,它确实如此不要做任何会导致拳击操作的事情)。调用 GetHashCode 的 Enum 版本实际上会导致装箱(正如您已经注意到的),因为它调用了一个返回 object 的内部方法,然后在其上调用 GetHashCode()
  • 顺便说一句,我在发布之前错误地将GetHashCode() 的实现检查为shortint 的实现就是 return this;,所以(正如 Raymond Chen 已经评论过的那样)您可以将 Enum 值转换为 int 并将其用作您的哈希码。
  • Enum 也覆盖了GetHashCode ——但拳击仍然会发生。您会在呼叫站点而不是目标站点看到拳击。
  • @BillyONeal,你是正确的,Enum 调用 GetHashCode() 的拳击正在调用现场发生。但是,这似乎是因为 System.Enum 是作为继承自 ValueType 的抽象类实现的,而 System.Int32 是一个结构。我将从我的编译代码中发布 IL 以向您展示正在发生的事情。
猜你喜欢
  • 2021-11-10
  • 1970-01-01
  • 2023-02-26
  • 1970-01-01
  • 2019-09-08
  • 1970-01-01
  • 2017-02-18
  • 2019-02-01
  • 1970-01-01
相关资源
最近更新 更多