Переопределение 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>