29

所以我试图弄清楚如何GetHashCode()在 VB 中正确覆盖大量自定义对象。稍微搜索一下,我就得到了这个美妙的答案

除了有一个问题:VB在.NET 4.0 中缺少checkedand关键字。unchecked据我所知,无论如何。因此,使用 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 太小了。我测试的特定实例将“名称”作为一个简单的 5 字符字符串,并且仅该哈希就足够接近 Int32 的上限,当它试图计算哈希的第二个字段(值)时,它会溢出。因为我找不到粒度checked/unchecked支持的 VB 等效项,所以我无法解决这个问题。

我也不想在整个项目中删除整数溢出检查。这件事可能已经完成了....40%(这是我编的,TBH),而且我还有很多代码要编写,所以我需要在相当长的一段时间内完成这些溢出检查。

Jon 的GetHashCodeVB 和 Int32 版本的“安全”版本是什么?或者,.NET 4.0 中是否有checked/unchecked我在 MSDN 上不太容易找到的地方?


编辑:
根据链接的 SO 问题,最底部不受欢迎的答案之一提供了解决方案。我说准是因为感觉它是……作弊。不过,乞丐不能挑剔,对吧?

从 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 的假设不断重复呢?  </修辞问题>

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


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

快速浏览一下 .NET 4.5 的变化表明他们还没有考虑到它,所以也许是 .NET 5?

下面是我的最终实现,它符合 GetHashCode 的约束,同时对于 VB 来说仍然足够快且足够独特,源自此页面上的“旋转哈希”示例:

'// 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”哈希也可能适用,但我还没有测试过。

4

7 回答 7

25

使用 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)
于 2011-01-11T11:03:16.587 回答
11

这是一个结合了Hans Passant 的回答Jon Skeet 的回答的实现。

它甚至适用于数百万个属性(即没有整数溢出异常)并且非常快(为具有 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
于 2015-10-07T16:43:28.770 回答
5

我在 vb.net 中实施 Skeet 先生的解决方案时遇到了同样的问题。我最终使用 Mod 运算符到达那里。Integer.MaxValue 的每个 Mod 应该只返回最不重要的组件,并且将始终在 Integer.MaxValue 和 Integer.MinValue 内——这应该与未选中具有相同的效果。您可能不必像我那样经常修改(只有当有机会变得大于 long 时(这意味着组合大量哈希码),然后在最后一次),但这是一个变体对我来说(并且让您可以像使用其他一些哈希函数一样使用更大的素数而不必担心)。

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
于 2015-09-14T07:44:12.557 回答
2

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

于 2011-01-11T10:25:54.547 回答
2

改进的答案在没有选中/未选中关键字支持的情况下覆盖 VB 中的 GetHashCode?

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

每次乘法后都有一个修整。And 文字被明确定义为 Long,因为带有 Integer 参数的 And 运算符不会将高字节归零。

于 2011-08-03T12:44:35.673 回答
2

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

但是,我用不同的解决方案解决了这个问题。一篇抱怨uncheckedVB 语言功能请求页面上缺少的帖子给了我使用框架中已经存在的哈希算法的想法。在我的问题中,我有一个String并且Guid我想将它用作字典键。我决定 aTupple(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 的可怜的开发人员有用。

干杯

于 2014-08-25T21:43:09.393 回答
1

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

  <PropertyGroup>
    <RemoveIntegerChecks>true</RemoveIntegerChecks>   
  </PropertyGroup>
于 2014-05-13T16:59:46.707 回答