所以我试图弄清楚如何在VB中为大量自定义对象正确覆盖GetHashCode()
。一些搜索引导我this wonderful answer。
除了一个问题:VB缺少.NET 4.0中的checked
和unchecked
关键字。据我所知,无论如何。因此,使用Jon Skeet的实现,我尝试在一个相当简单的类上创建这样的覆盖,该类有三个主要成员:Name As String
,Value 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#的< / rhetorical-question> unchecked
关键字一样,只会导致不同的操作码被发出。那么,为什么我继续看到这两个假设都产生完全相同的IL不断重复?
无论如何,我宁愿找到一个可以在每个对象模块中实现的解决方案。从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”哈希也可能适用,但我还没有测试过。
答案 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 answer和Jon 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
的帖子让我想到了使用框架中已有的哈希算法。在我的问题中,我有一个String
和Guid
我想用于字典键。我决定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的可怜的开发者来说很有用。
干杯