Переопределение GetHashCode в VB без проверки/непроверенной поддержки ключевых слов?
Итак, я пытаюсь выяснить, как правильно переопределить GetHashCode()
в VB для большого количества пользовательских объектов. Немного поиска приводит меня к этому замечательному ответу.
За исключением одной проблемы: VB не хватает как ключевого слова checked
, так и unchecked
в .NET 4.0. Насколько я могу судить, так или иначе. Поэтому, используя реализацию 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 слишком мал для даже простого объекта, такого как этот. В конкретном экземпляре, который я тестировал, есть "Имя" как простая 5-символьная строка, и этот хеш был достаточно близок к верхнему пределу Int32, что, когда он пытался вычислить второе поле хэша (Value), он переполнился. Поскольку я не могу найти эквивалент VB для гранулярной поддержки checked
/unchecked
, я не могу обойти это.
Я также не хочу удалять проверки переполнения Integer по всему проекту. Эта вещь может быть.... 40% в полном объеме (я сделал это, TBH), и у меня есть намного больше кода для написания, поэтому мне нужны эти проверки переполнения на месте в течение довольно долгого времени.
Какова была бы "безопасная" версия версии Jon GetHashCode
для VB и Int32? Или .NET 4.0 имеет checked
/unchecked
в нем где-то, что я не нахожу очень легко на MSDN?
EDIT:
В рамках связанного вопроса SO один из нелюбимых ответов в самом низу обеспечивал квазирешение. Я говорю квази, потому что кажется, что это... обман. Однако нищие не могут быть выборами, верно?
Переведенный с С# в более читаемый VB и выровненный с объектом, описанным выше (Name, Value, Type), получаем:
Public Overrides Function GetHashCode() As Int32
Return New With { _
Key .A = _Name, _
Key .B = _Value, _
Key .C = _Type
}.GetHashCode()
End Function
Это приводит к тому, что компилятор, по-видимому, "обманывает", создавая анонимный тип, который затем компилируется за пределами пространства имен проекта, предположительно с проверкой целостности целых чисел, и позволяет математике проходить и просто обертываться, когда она переполняется. Он также, по-видимому, включает в себя box
коды операций, которые, как я знаю, являются хитами производительности. Однако нет распаковки.
Но это вызывает интересный вопрос. Бесчисленное количество раз, я видел, что здесь и в другом месте сказано, что и VB, и С# генерируют один и тот же IL-код. Это явно не так в 100% случаев... Как и использование ключевого слова С# unchecked
, просто приводит к исходу другого кода операции. Итак, почему я продолжаю видеть предположение, что оба продукта повторяют то же самое, что и IL? </rhetorical-question >
В любом случае, я бы предпочел найти решение, которое может быть реализовано в каждом объектном модуле. Создание анонимных типов для каждого из моих объектов будет выглядеть беспорядочно с точки зрения ILDASM. Я не шучу, когда говорю, что в моем проекте реализовано много классов.
EDIT2: Я обнаружил ошибку в MSFT Connect, и суть результата PM VB заключалась в том, что они рассмотрят это, но не задерживайте дыхание:
https://connect.microsoft.com/VisualStudio/feedback/details/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" также может применяться, но я его не тестировал.
Ответы
Ответ 1
Используйте 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)
Ответ 2
Вот реализация, сочетающая ответ Hans Passant и ответ Jon Skeet.
Он работает даже для миллионов свойств (т.е. исключений целых переполнений) и очень быстро (менее 20 мс для генерации хеш-кода для класса с 1 000 000 полей и едва измеримого для класса с только 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
Ответ 3
У меня была такая же проблема с внедрением решения г-на Скита в vb.net. Я закончил тем, что использовал оператора Mod, чтобы туда добраться. Каждый Mod от Integer.MaxValue должен возвращать только наименее значимый компонент до этой точки и всегда будет находиться внутри Integer.MaxValue и Integer.MinValue - который должен иметь тот же эффект, что и unchecked. Вы, вероятно, не должны так часто меняться (это только когда есть шанс получить больше, чем длинный (что означало бы объединение LOT хэш-кодов), а затем один раз в конце), но вариант этого работает для меня (и позволяет играть с использованием гораздо больших простых чисел, таких как некоторые другие хэш-функции, не беспокоясь).
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
Ответ 4
Вы можете реализовать подходящий хеш-хэш в отдельной сборке либо с использованием С#, и с ключевым словом unchecked
, либо путем проверки переполнения для всего проекта (возможно как в проектах VB.NET, так и в С#). Если вы хотите, вы можете использовать ilmerge
для объединения этой сборки в свою основную сборку.
Ответ 5
Улучшенный ответ Переопределение GetHashCode в VB без проверки ключевого слова без проверки/непроверки?
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
После каждого умножения происходит обрезка. И букваль явно определяется как Long, потому что оператор And с аргументом Integer не обнуляет верхние байты.
Ответ 6
После исследования того, что VB не дал нам ничего подобного unchecked
и бушует для бит (С# dev теперь делает vb), я внедрил решение, близкое к тому, которое отправил Hans Passant. Я потерпел неудачу. Ужасная работа. Это, безусловно, было связано с моей реализацией, а не с решением, которое Ханс написал. Я мог бы вернуться и более точно скопировать его решение.
Однако я решил проблему с другим решением. Сообщение, жалующееся на отсутствие unchecked
на странице запросов функций языка VB, дало мне идею использовать хэш-алгоритм уже в рамках. В моей проблеме у меня были 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, который сталкивается с этим сообщением.
Приветствия
Ответ 7
Я также обнаружил, что свойство RemoveIntegerChecks MsBuild влияет на /removeintchecks Компилятор VB свойство, которое мешает компилятору испускать проверки выполнения:
<PropertyGroup>
<RemoveIntegerChecks>true</RemoveIntegerChecks>
</PropertyGroup>