vb.net - valores - ¿Anula GetHashCode en VB sin soporte de palabras clave comprobado/no verificado?
tipos de excepciones en visual basic (7)
Así que estoy tratando de descubrir cómo sobrescribir correctamente GetHashCode()
en VB para una gran cantidad de objetos personalizados. Un poco de búsqueda me lleva a esta maravillosa respuesta .
Excepto que hay un problema: VB carece de la palabra clave checked
y unchecked
en .NET 4.0. Por lo que puedo decir, de todos modos. Entonces, al usar la implementación de Jon Skeet, intenté crear una anulación así en una clase bastante simple que tiene tres miembros principales: Name As String
, Value As Int32
, y [Type] As System.Type
. Así que se me ocurre:
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
Problema: Int32 es demasiado pequeño incluso para un objeto simple como este. La instancia particular que probé tiene "Nombre" como una cadena simple de 5 caracteres, y ese hash solo estaba lo suficientemente cerca del límite superior de Int32, que cuando intentó calcificar el segundo campo del hash (Valor), se desbordó. Debido a que no puedo encontrar un equivalente de VB para compatibilidad granular checked
/ unchecked
, no puedo evitar esto.
Tampoco quiero eliminar las comprobaciones de desbordamiento de enteros en todo el proyecto. Esto quizás esté ... 40% completo (lo inventé, TBH), y tengo mucho más código para escribir, así que necesito estos controles de desbordamiento en su lugar por bastante tiempo.
¿Cuál sería la versión "segura" de la versión GetHashCode
de Jon para VB e Int32? O, ¿.NET 4.0 ha checked
/ unchecked
en algún lugar que no encuentro muy fácilmente en MSDN?
EDITAR:
Según la pregunta SO relacionada, una de las respuestas no queridas en la parte inferior proporcionó una cuasi- resolución. Digo casi porque parece que ... es hacer trampa. Los mendigos no pueden ser selectivos, ¿verdad?
Traducido de C # a un VB más legible y alineado con el objeto descrito anteriormente (Nombre, Valor, Tipo), obtenemos:
Public Overrides Function GetHashCode() As Int32
Return New With { _
Key .A = _Name, _
Key .B = _Value, _
Key .C = _Type
}.GetHashCode()
End Function
Esto hace que el compilador aparentemente "engañe" al generar un tipo anónimo, que luego compila fuera del espacio de nombres del proyecto, presumiblemente con las comprobaciones de desbordamiento de entero deshabilitadas, y permite que las operaciones matemáticas ocurran y simplemente se envuelven cuando se desborda. También parece involucrar códigos de box
, que sé que son éxitos de rendimiento. Sin embargo, no hay unboxing.
Pero esto plantea una pregunta interesante. Innumerables veces, lo he visto aquí y en otras partes que tanto VB como C # generan el mismo código IL. Claramente, este no es el caso el 100% del tiempo ... Al igual que el uso de la palabra clave unchecked
marcar de C # simplemente causa que se emita un código de operación diferente.Entonces, ¿por qué sigo viendo la suposición de que ambos producen la misma IL repetición? </ retórica-pregunta>
De todos modos, prefiero encontrar una solución que pueda implementarse dentro de cada módulo de objetos. Tener que crear tipos anónimos para cada uno de mis objetos va a parecer desordenado desde una perspectiva ILDASM. No estoy bromeando cuando digo que tengo muchas clases implementadas en mi proyecto.
EDIT2: Abrí un error en MSFT Connect, y la esencia del resultado del VB PM fue que lo considerarán, pero no contengan la respiración: https://connect.microsoft.com/VisualStudio/feedback/details/636564/checked-unchecked-keywords-in-visual-basic
Un vistazo rápido a los cambios en .NET 4.5 sugiere que aún no lo han considerado, entonces ¿quizás .NET 5?
Mi implementación final, que se ajusta a las limitaciones de GetHashCode, sin dejar de ser lo suficientemente rápida y única para VB, se encuentra debajo, derivada del ejemplo de "Rotación giratoria" en esta página :
''// 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
También creo que el hash "Shift-Add-XOR" también puede aplicarse, pero no lo he probado.
Aquí hay una implementación que combina la respuesta de Hans Passant y la respuesta de Jon Skeet .
Funciona incluso para millones de propiedades (es decir, sin excepciones de desbordamiento de enteros) y es muy rápido (menos de 20 ms para generar código hash para una clase con 1,000,000 campos y apenas medible para una clase con solo 100 campos).
Aquí está la estructura para manejar los desbordamientos:
<StructLayout(LayoutKind.Explicit)>
Private Structure HashCodeNoOverflow
<FieldOffset(0)> Public Int64 As Int64
<FieldOffset(0)> Public Int32 As Int32
End Structure
Y una simple función 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
O si lo prefiere:
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
Después de investigar que VB no nos había dado nada como unchecked
y furioso por un tiempo (c # dev ahora haciendo vb), implementé una solución cercana a la publicada por Hans Passant. Fallé en eso. Terrible rendimiento. Esto fue sin duda debido a mi implementación y no a la solución que Hans publicó. Podría haber retrocedido y copiar más de cerca su solución.
Sin embargo, resolví el problema con una solución diferente. Una publicación quejándose de la falta de unchecked
en la página de solicitudes de características del lenguaje VB me dio la idea de usar un algoritmo hash ya en el marco. En mi problema, tenía un String
and Guid
que quería usar para una clave de diccionario. Decidí que un Tupple(Of Guid, String)
sería una excelente tienda interna de datos.
Versión original mala
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
Versión muy mejorada
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
Por lo tanto, aunque espero que haya soluciones mucho mejores que esta, estoy bastante contento. Mi desempeño es bueno. Además, el desagradable código de utilidad se ha ido. Esperemos que esto sea útil para otros desarrolladores pobres que se ven obligados a escribir a VB que se encuentra con esta publicación.
Aclamaciones
Puede implementar un asistente de código hash adecuado en un ensamblaje separado, ya sea usando C # y la palabra clave unchecked
o la comprobación de desbordamiento para todo el proyecto (posible en ambos proyectos VB.NET y C #). Si lo desea, puede usar ilmerge
para fusionar este ensamblaje con su ensamblaje principal.
Respuesta mejorada ¿ Reemplazando GetHashCode en VB sin soporte de palabras clave comprobado / no verificado?
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
Hay un recorte después de cada multiplicación. Y el literal se define explícitamente como Largo porque el operador Y con un argumento entero no pone a cero los bytes superiores.
También descubrí que la propiedad RemoveIntegerChecks MsBuild afecta /removeintchecks propiedad del compilador de VB que impide que el compilador /removeintchecks comprobaciones en tiempo de ejecución:
<PropertyGroup>
<RemoveIntegerChecks>true</RemoveIntegerChecks>
</PropertyGroup>
Tuve el mismo problema al implementar la solución del Sr. Skeet en vb.net. Terminé usando el operador Mod para llegar allí. Cada Mod por Integer.MaxValue debería devolver el componente menos significativo hasta ese punto y siempre estará dentro de Integer.MaxValue y Integer.MinValue, que debería tener el mismo efecto que si no se hubiera marcado. Probablemente no tengas que modificar tanto como yo (solo cuando hay una posibilidad de que sea más grande que una larga (lo que significaría combinar MUCHOS códigos hash) y luego una vez al final), pero una variante de esto funciona para mí (y te permite jugar usando primos mucho más grandes como algunas de las otras funciones hash sin preocuparte).
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
Use Long para evitar el desbordamiento:
Dim hash As Long = 17
'''' etc..
Return CInt(hash And &H7fffffffL)
El operador Y garantiza que no se lanzará ninguna excepción de desbordamiento. Sin embargo, esto pierde un poco de "precisión" en el código hash calculado, el resultado siempre es positivo. VB.NET no tiene una función incorporada para evitarlo, pero puede usar un truco:
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
Ahora puedes escribir:
Dim hash As Long = 17
'''' etc..
Return NoOverflows.LongToInteger(hash)