在没有选中/取消选中关键字支持的情况下覆盖VB中的GetHashCode?

时间:2011-01-11 04:37:46

标签: vb.net gethashcode

所以我试图弄清楚如何在VB中为大量自定义对象正确覆盖GetHashCode()。一些搜索引导我this wonderful answer

除了一个问题:VB缺少.NET 4.0中的checkedunchecked关键字。据我所知,无论如何。因此,使用Jon Skeet的实现,我尝试在一个相当简单的类上创建这样的覆盖,该类有三个主要成员:Name As StringValue As Int32[Type] As System.Type。因此我想出了:

Public Overrides Function GetHashCode() As Int32
    Dim hash As Int32 = 17

    hash = hash * 23 + _Name.GetHashCode()
    hash = hash * 23 + _Value
    hash = hash * 23 + _Type.GetHashCode()
    Return hash
End Function

问题:即使像这样的简单对象,Int32也太小了。我测试的特定实例将“Name”作为一个简单的5个字符的字符串,并且该哈希值足够接近Int32的上限,当它试图计算哈希值(Value)的第二个字段时,它会溢出。因为我无法找到粒度checked / unchecked支持的VB等价物,所以我无法解决这个问题。

我也不想删除整个项目中的Integer溢出检查。这个东西可能...... 40%完成(我做了这个,TBH),我有更多的代码要写,所以我需要这些溢出检查已经有一段时间了。

对于VB和Int32,Jon的GetHashCode版本的“安全”版本是什么?或者,.NET 4.0在某个地方有checked / unchecked,我在MSDN上很难找到它吗?


修改
根据链接的SO问题,最底部的unloved answers之一提供了 - 解决方案。我说准,因为它感觉像是......作弊。乞丐不能选择,对吗?

从C#转换为更易读的VB并与上述对象(名称,值,类型)对齐,我们得到:

Public Overrides Function GetHashCode() As Int32
    Return New With { _
        Key .A = _Name, _
        Key .B = _Value, _
        Key .C = _Type
     }.GetHashCode()
End Function

这会触发编译器显然通过生成匿名类型来“欺骗”,然后它会在项目命名空间之外进行编译,可能会禁用整数溢出检查,并允许数学发生并在溢出时简单地回绕。它似乎也涉及box操作码,我知道这是操作性能。不过没有取消装箱。

但这提出了一个有趣的问题。无数次,我已经看到它在这里和其他地方声明VB和C#都生成相同的IL代码。这显然不是100%的情况......就像使用C#的unchecked关键字一样,只会导致不同的操作码被发出。那么,为什么我继续看到这两个假设都产生完全相同的IL不断重复?< / rhetorical-question>

无论如何,我宁愿找到一个可以在每个对象模块中实现的解决方案。从ILDASM的角度来看,必须为我的每一个对象创建匿名类型都会变得混乱。当我说我的项目中实现了 lot 类时,我不是在开玩笑。


EDIT2:我确实在MSFT Connect上打开了一个错误,VB PM结果的要点是他们会考虑它,但不要屏住呼吸: https://connect.microsoft.com/VisualStudio/feedback/details/636564/checked-unchecked-keywords-in-visual-basic

快速浏览.NET 4.5中的更改表明他们还没有考虑过,所以也许.NET 5?

我的最终实现符合GetHashCode的约束,同时仍然快速且足够唯一用于VB,下面是this page上的“旋转哈希”示例:

'// The only sane way to do hashing in VB.NET because it lacks the
'// checked/unchecked keywords that C# has.
Public Const HASH_PRIME1 As Int32 = 4
Public Const HASH_PRIME2 As Int32 = 28
Public Const INT32_MASK As Int32 = &HFFFFFFFF

Public Function RotateHash(ByVal hash As Int64, ByVal hashcode As Int32) As Int64
    Return ((hash << HASH_PRIME1) Xor (hash >> HASH_PRIME2) Xor hashcode)
End Function

我也认为“Shift-Add-XOR”哈希也可能适用,但我还没有测试过。

7 个答案:

答案 0 :(得分:22)

使用Long来避免溢出:

Dim hash As Long = 17
'' etc..
Return CInt(hash And &H7fffffffL)

And运算符确保不会抛出溢出异常。然而,这确实在计算的哈希码中失去了一点“精度”,结果总是正的。 VB.NET没有内置函数来避免它,但你可以使用一个技巧:

Imports System.Runtime.InteropServices

Module NoOverflows
    Public Function LongToInteger(ByVal value As Long) As Integer
        Dim cast As Caster
        cast.LongValue = value
        Return cast.IntValue
    End Function

    <StructLayout(LayoutKind.Explicit)> _
    Private Structure Caster
        <FieldOffset(0)> Public LongValue As Long
        <FieldOffset(0)> Public IntValue As Integer
    End Structure
End Module

现在你可以写:

Dim hash As Long = 17
'' etc..
Return NoOverflows.LongToInteger(hash)

答案 1 :(得分:9)

以下是一个结合Hans Passant's answerJon Skeet's answer的实现。

它甚至可以用于数百万个属性(即没有整数溢出异常)并且非常快(对于具有1,000,000个字段的类生成哈希代码少于20毫秒,对于只有100个字段的类几乎不可测量)。

这是处理溢出的结构:

<StructLayout(LayoutKind.Explicit)>
Private Structure HashCodeNoOverflow
    <FieldOffset(0)> Public Int64 As Int64
    <FieldOffset(0)> Public Int32 As Int32
End Structure

一个简单的GetHashCode函数:

Public Overrides Function GetHashCode() As Integer

    Dim hashCode As HashCodeNoOverflow

    hashCode.Int64 = 17

    hashCode.Int64 = CLng(hashCode.Int32) * 23 + Field1.GetHashCode
    hashCode.Int64 = CLng(hashCode.Int32) * 23 + Field2.GetHashCode
    hashCode.Int64 = CLng(hashCode.Int32) * 23 + Field3.GetHashCode

    Return hashCode.Int32

End Function

或者如果您愿意:

Public Overrides Function GetHashCode() As Integer

    Dim hashCode = New HashCodeNoOverflow With {.Int32 = 17}

    For Each field In Fields
        hashCode.Int64 = CLng(hashCode.Int32) * 23 + field.GetHashCode
    Next

    Return hashCode.Int32

End Function

答案 2 :(得分:5)

我在vb.net中实现Skeet先生的解决方案时遇到了同样的问题。我最终使用Mod运算符到达那里。每个Mod by Integer.MaxValue应该只返回到该点的最不重要的组件,并且将始终在Integer.MaxValue和Integer.MinValue中 - 这应该与未选中的效果相同。你可能没有像我那样经常修改(只有当它有可能变得比一个长的大(这意味着要结合很多哈希码)然后一次结束时),但是这个的一个变种对我来说(并且让你玩一些像其他散列函数一样使用更大的素数而不用担心)。

Public Overrides Function GetHashCode() As Int32
    Dim hash as Int64 = 17
    hash = (hash * 23 + _Name.GetHashCode()) Mod Integer.MaxValue
    hash = (hash * 23 + _Value) Mod Integer.MaxValue
    hash = (hash * 23 + _Type.GetHashCode()) Mod Integer.MaxValue
    Return Convert.ToInt32(hash)
End Function

答案 3 :(得分:2)

您可以使用C#和unchecked关键字在单独的程序集中实现合适的哈希代码帮助程序,或者为整个项目转换溢出检查(可能在VB.NET和C#项目中)。如果您愿意,可以使用ilmerge将此程序集合并到主程序集。

答案 4 :(得分:2)

改进了答案Overriding GetHashCode in VB without checked/unchecked keyword support?

Public Overrides Function GetHashCode() as Integer
  Dim hashCode as Long = 0
  If myReplacePattern IsNot Nothing Then _
    hashCode = ((hashCode*397) Xor myField.GetHashCode()) And &HffffffffL
  If myPattern IsNot Nothing Then _
    hashCode = ((hashCode*397) Xor myOtherField.GetHashCode()) And &HffffffffL
  Return CInt(hashCode)
End Function

每次乘法后都会进行修剪。并且literal显式定义为Long,因为带有Integer参数的And运算符不会将高位字节归零。

答案 5 :(得分:1)

我还发现 RemoveIntegerChecks MsBuild属性影响/removeintchecks VB编译器属性,阻止编译器发出运行时检查:

  <PropertyGroup>
    <RemoveIntegerChecks>true</RemoveIntegerChecks>   
  </PropertyGroup>

答案 6 :(得分:1)

在研究了VB没有给我们任何类似unchecked之类的东西并且稍微肆虐(c#dev现在正在做vb)之后,我实施了一个接近Hans Passant发布的解决方案。我失败了。表现糟糕。这当然是由于我的实施,而不是Hans发布的解决方案。我本可以回去并更密切地复制他的解决方案。

但是,我用不同的解决方案解决了这个问题。一篇关于VB语言功能请求页面缺少unchecked的帖子让我想到了使用框架中已有的哈希算法。在我的问题中,我有一个StringGuid我想用于字典键。我决定Tupple(Of Guid, String)是一个很好的内部数据存储。

原始版本

Public Structure HypnoKey
  Public Sub New(name As String, areaId As Guid)
    _resourceKey = New Tuple(Of Guid, String)(resourceAreaId, key)
  End Sub

  Private ReadOnly _name As String
  Private ReadOnly _areaId As Guid

  Public ReadOnly Property Name As String
    Get
      Return _name 
    End Get
  End Property

  Public ReadOnly Property AreaId As Guid
    Get
      Return _areaId 
    End Get
  End Property

  Public Overrides Function GetHashCode() As Integer
    'OMFG SO BAD
    'TODO Fail less hard
  End Function

End Structure

许多改进版本

Public Structure HypnoKey
  Public Sub New(name As String, areaId As Guid)
    _innerKey = New Tuple(Of Guid, String)(areaId , key)
  End Sub

  Private ReadOnly _innerKey As Tuple(Of Guid, String)

  Public ReadOnly Property Name As String
    Get
      Return _innerKey.Item2
    End Get
  End Property

  Public ReadOnly Property AreaId As Guid
    Get
      Return _innerKey.Item1
    End Get
  End Property

  Public Overrides Function GetHashCode() As Integer
    Return _innerKey.GetHashCode() 'wow! such fast (enuf)
  End Function

End Structure

所以,虽然我希望有比这更好的解决方案,但我很高兴。我的表现很好。此外,令人讨厌的实用程序代码消失了。希望这对于其他一些被迫编写VB的可怜的开发者来说很有用。

干杯