Overriding GetHashCode in VB without checked/unchecked keyword support?

前端 未结 7 1807
庸人自扰
庸人自扰 2020-12-05 05:00

So I\'m trying to figure out how to correctly override GetHashCode() in VB for a large number of custom objects. A bit of searching leads me to this wonderful

相关标签:
7条回答
  • 2020-12-05 05:09

    After researching that VB had not given us anything like unchecked and raging for a bit (c# dev now doing vb), I implemented a solution close to the one Hans Passant posted. I failed at it. Terrible performance. This was certainly due to my implementation and not the solution Hans posted. I could have gone back and more closely copied his solution.

    However, I solved the problem with a different solution. A post complaining about lack of unchecked on the VB language feature requests page gave me the idea to use a hash algorithm already in the framework. In my problem, I had a String and Guid that I wanted to use for a dictionary key. I decided a Tupple(Of Guid, String) would be a fine internal data store.

    Original Bad Version

    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
    

    Much Improved Version

    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
    

    So, while I expect there are far better solutions than this, I am pretty happy. My performance is good. Also, the nasty utility code is gone. Hopefully this is useful to some other poor dev forced to write VB who comes across this post.

    Cheers

    0 讨论(0)
  • 2020-12-05 05:19

    Improved answer 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
    

    There is a trimming after each multiplication. And literal is defined explicitly as Long because the And operator with an Integer argument does not zeroize the upper bytes.

    0 讨论(0)
  • 2020-12-05 05:19

    I've also found that RemoveIntegerChecks MsBuild property affects /removeintchecks VB compiler property that prevents compiler from emitting runtime checks:

      <PropertyGroup>
        <RemoveIntegerChecks>true</RemoveIntegerChecks>   
      </PropertyGroup>
    
    0 讨论(0)
  • 2020-12-05 05:29

    Use Long to avoid the overflow:

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

    The And operator ensures no overflow exception is thrown. This however does lose one bit of "precision" in the computed hash code, the result is always positive. VB.NET has no built-in function to avoid it, but you can use a trick:

    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
    

    Now you can write:

    Dim hash As Long = 17
    '' etc..
    Return NoOverflows.LongToInteger(hash)
    
    0 讨论(0)
  • 2020-12-05 05:30

    I had the same problem implementing Mr. Skeet's solution in vb.net. I ended up using the Mod operator to get there. Each Mod by Integer.MaxValue should return just the least significant component up to that point and will always be within Integer.MaxValue and Integer.MinValue -- which should have the same effect as unchecked. You probably don't have to mod as often as I do (it's only when there's a chance of getting bigger than a long (which would mean combining a LOT of hash codes) and then once at the end) but a variant of this works for me (and lets you play with using much bigger primes like some of the other hash functions without worrying).

    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
    
    0 讨论(0)
  • 2020-12-05 05:31

    Here is an implementation combining Hans Passant's answer and Jon Skeet's answer.

    It works even for millions of properties (i.e. no integer overflow exceptions) and is very fast (less than 20 ms to generate hash code for a class with 1,000,000 fields and barely measurable for a class with only 100 fields).

    Here is the structure to handle the overflows:

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

    And a simple GetHashCode function:

    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
    

    Or if your prefer:

    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
    
    0 讨论(0)
提交回复
热议问题