vb.net - practice - unity override gethashcode




Überschreiben von GetHashCode in VB ohne Überprüfung/ungeprüfte Keyword-Unterstützung? (5)

Hier ist eine Implementierung, die Hans Passants Antwort und Jon Skeets Antwort kombiniert.

Es funktioniert sogar für Millionen von Eigenschaften (dh keine ganzzahligen Überlaufausnahmen) und ist sehr schnell (weniger als 20 ms, um Hash-Code für eine Klasse mit 1.000.000 Feldern zu erzeugen und kaum messbar für eine Klasse mit nur 100 Feldern).

Hier ist die Struktur, um die Überläufe zu behandeln:

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

Und eine einfache GetHashCode-Funktion:

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

Oder wenn Sie bevorzugen:

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

Also versuche ich herauszufinden, wie man GetHashCode() in VB für eine große Anzahl von benutzerdefinierten Objekten richtig überschreiben GetHashCode() . Ein bisschen Suchen führt mich zu dieser wundervollen Antwort .

Abgesehen davon gibt es ein Problem: In .NET 4.0 fehlen sowohl das checked als auch das unchecked Schlüsselwort. Soweit ich das beurteilen kann, jedenfalls. Mit der Implementierung von Jon Skeet habe ich versucht, eine solche Überschreibung für eine ziemlich einfache Klasse zu erstellen, die aus drei Value As Int32 besteht: Name As String Value As Int32 , Value As Int32 und [Type] As System.Type . So komme ich auf:

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

Problem: Int32 ist selbst für ein einfaches Objekt zu klein. Die bestimmte Instanz, die ich getestet habe, hat "Name" als eine einfache 5-stellige Zeichenkette, und dieser Hash war nahe genug an der Obergrenze von Int32, dass wenn er versuchte, das zweite Feld des Hash (Value) zu berechnen, übergelaufen ist. Da ich kein VB-Äquivalent für die granulare checked / unchecked Unterstützung finden kann, kann ich das nicht umgehen.

Ich möchte auch keine Integer-Überlaufprüfungen über das gesamte Projekt hinweg entfernen. Diese Sache ist vielleicht .... 40% abgeschlossen (ich habe das gemacht, TBH), und ich habe viel mehr Code zu schreiben, also brauche ich diese Überlauf-Checks für eine ganze Weile.

Was wäre die "sichere" Version von Jon's GetHashCode Version für VB und Int32? Oder hat .NET 4.0 irgendwo, das ich auf MSDN nicht sehr leicht finde, ein Häkchen gesetzt?


BEARBEITEN:
Nach der verknüpften SO-Frage bot eine der ungeliebten Antworten ganz unten eine Quasi- Lösung. Ich sage quasi, weil es sich anfühlt als ob es ... betrügt. Bettler können aber nicht wählerisch sein, oder?

Von C # in eine besser lesbare VB übersetzt und auf das oben beschriebene Objekt (Name, Wert, Typ) ausgerichtet, erhalten wir:

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

Dies führt dazu, dass der Compiler scheinbar "schummelt", indem er einen anonymen Typ erzeugt, den er dann außerhalb des Projekt-Namespace kompiliert, vermutlich mit deaktivierten Integer-Überlauf-Checks, und die Mathe ermöglicht und einfach umgeht, wenn er überläuft. Es scheint auch box Opcodes zu beinhalten, von denen ich weiß, dass sie Performance-Hits sind. Kein Unboxing, obwohl.

Aber das wirft eine interessante Frage auf. Unzählige Male habe ich hier und anderswo festgestellt, dass sowohl VB als auch C # den gleichen IL-Code generieren. Dies ist natürlich nicht in 100% der Fälle der Fall ... Wie die Verwendung des unchecked Schlüsselworts von C # bewirkt, dass ein anderer Opcode ausgegeben wird. Also warum sehe ich weiterhin die Annahme, dass beide die gleiche IL produzieren, die sich wiederholen? </ rhetorische Frage>

Wie auch immer, ich würde lieber eine Lösung finden, die innerhalb jedes Objektmoduls implementiert werden kann. Anonyme Typen für jedes einzelne meiner Objekte zu erstellen wird aus einer ILDASM-Perspektive unordentlich aussehen. Ich mache keine Witze, wenn ich sage, dass ich viele Klassen in meinem Projekt implementiert habe.


EDIT2: Ich habe einen Fehler auf MSFT Connect geöffnet, und das Wesentliche des Ergebnisses von VB PM war, dass sie es in Betracht ziehen werden, aber halte nicht den Atem an: https://connect.microsoft.com/VisualStudio/feedback/details/636564/checked-unchecked-keywords-in-visual-basic

Ein kurzer Blick auf die Änderungen in .NET 4.5 legt nahe, dass sie es noch nicht berücksichtigt haben, also vielleicht .NET 5?

Meine letzte Implementierung, die zu den Beschränkungen von GetHashCode passt, während sie immer noch schnell und einzigartig genug für VB ist, ist von dem Beispiel "Rotating Hash" auf dieser Seite abgeleitet :

'// 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

Ich denke auch, dass der "Shift-Add-XOR" Hash auch angewendet werden kann, aber ich habe es nicht getestet.


Ich habe auch festgestellt, dass RemoveIntegerChecks MsBuild -Eigenschaft Auswirkungen /removeintchecks VB-Compiler-Eigenschaft, die verhindert, dass Compiler Laufzeitprüfungen /removeintchecks :

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

Nachdem ich nachgeforscht hatte, dass VB uns für ein bisschen nichts unchecked und toben ließ (c # dev macht jetzt vb), implementierte ich eine Lösung, die der von Hans Passant geposteten ähnlich war. Ich habe es versäumt. Schreckliche Leistung. Dies lag sicherlich an meiner Implementierung und nicht an der von Hans geposteten Lösung. Ich hätte zurückgehen und seine Lösung näher kopieren können.

Ich löste das Problem jedoch mit einer anderen Lösung. Ein Beitrag, der sich über das Fehlen von unchecked Seiten auf der Seite der VB-Sprachenfeatures beschwerte, gab mir die Idee, einen Hash-Algorithmus zu verwenden, der bereits im Framework enthalten ist. In meinem Problem hatte ich eine String und eine Guid , die ich für einen Wörterbuchschlüssel verwenden wollte. Ich entschied, dass ein Tupple(Of Guid, String) ein guter interner Datenspeicher wäre.

Ursprüngliche schlechte 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

Viel verbesserte 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

Also, während ich erwarte, dass es viel bessere Lösungen als das gibt, bin ich ziemlich glücklich. Meine Leistung ist gut. Außerdem ist der böse Dienstprogramm-Code weg. Hoffentlich ist das nützlich für einen anderen armen Entwickler, der gezwungen wird, VB zu schreiben, der auf diesen Beitrag stößt.

Prost


Sie können einen geeigneten Hash-Codehelfer in einer separaten Assembly implementieren, indem Sie C # und das unchecked Schlüsselwort verwenden oder die Überlaufprüfung für das gesamte Projekt aktivieren (möglich in VB.NET- und C # -Projekten). Wenn Sie möchten, können Sie mit ilmerge diese Assembly mit Ihrer Hauptbaugruppe zusammenführen.


Verwenden Sie Long, um den Überlauf zu vermeiden:

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

Der Und-Operator stellt sicher, dass keine Überlauf-Ausnahme ausgelöst wird. Dies verliert jedoch im berechneten Hash-Code ein Bit "Präzision", das Ergebnis ist immer positiv. VB.NET hat keine eingebaute Funktion, um es zu vermeiden, aber Sie können einen Trick verwenden:

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

Jetzt können Sie schreiben:

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




gethashcode