Переопределение GetHashCode в VB без поддержки отмеченных / непроверенных ключевых слов? - PullRequest
23 голосов
/ 11 января 2011

Поэтому я пытаюсь выяснить, как правильно переопределить GetHashCode() в VB для большого количества пользовательских объектов.Немного поиска приводит меня к этому замечательному ответу .

За исключением одной проблемы: в VB отсутствует ключевое слово checked и unchecked в .NET 4.0.Насколько я могу сказать, в любом случае.Поэтому, используя реализацию Джона Скита, я попытался создать такое переопределение для довольно простого класса, который имеет три основных члена: 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, поэтому при попытке вычислить второе поле хеша (Значение) он переполнился.Поскольку я не могу найти VB-эквивалент для гранулярной поддержки checked / unchecked, я не могу обойти это.

Я также не хочу удалять проверки переполнения целых чисел во всем проекте.Возможно, эта штука завершена на 40% (я это придумал, TBH), и у меня есть гораздо больше кода, чтобы написать, поэтому мне нужны эти проверки переполнения в течение достаточно долгого времени.

Чтобудет «безопасной» версией Jon GetHashCode для VB и Int32?Или в .NET 4.0 есть где-то checked / unchecked, что мне не очень легко найти в MSDN? РЕДАКТИРОВАТЬ:
Согласно связанному вопросу SO, один из нелюбимых ответов в самом низу обеспечил квази -решение.Я говорю квази, потому что такое чувство, что это ... обман.Хотя нищие не могут быть селекторами, верно?

Переведенный из C # в более читаемый 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, и C # генерируют один и тот же код IL.Это явно не так в 100% случаев ... Как и использование ключевого слова unchecked в C #, просто вызывает другой код операции.Так почему же я продолжаю видеть предположение, что оба производят один и тот же IL, повторяется?

В любом случае, я бы предпочел найти решение, которое может быть реализовано в каждом объектном модуле.Необходимость создания анонимных типов для каждого из моих объектов будет выглядеть беспорядочно с точки зрения ILDASM.Я не шучу, когда говорю, что в моем проекте реализовано много классов. EDIT2: Я открыл ошибку в MSFT Connect, и суть результата от VB PM заключалась в том, что они его рассмотрят, но не затаили дыхание: 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"Хеш может также применяться, но я не проверял это.

Ответы [ 7 ]

22 голосов
/ 11 января 2011

Используйте 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)
9 голосов
/ 07 октября 2015

Вот реализация, объединяющая ответ Ханса Пассанта и ответ Джона Скита .

Он работает даже для миллионов свойств (т.е. без исключений целочисленного переполнения) и очень быстр (менее 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
5 голосов
/ 14 сентября 2015

У меня была такая же проблема при реализации решения мистера Скита в vb.net.Я закончил тем, что использовал оператор Mod, чтобы добраться туда.Каждый Mod by Integer.MaxValue должен возвращать только наименее значимый компонент до этой точки и всегда будет в пределах Integer.MaxValue и Integer.MinValue - что должно иметь тот же эффект, что и непроверенный.Вам, вероятно, не нужно модировать так часто, как я (это только тогда, когда есть шанс получить больше, чем long (что будет означать объединение множества хеш-кодов), а затем один раз в конце), но вариант этого работаетдля меня (и позволяет вам играть с использованием гораздо больших простых чисел, как некоторые другие хэш-функции, не беспокоясь).

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
2 голосов
/ 03 августа 2011

Улучшенный ответ Переопределение 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 не обнуляет старшие байты.

2 голосов
/ 11 января 2011

Подходящий помощник по хеш-коду можно реализовать в отдельной сборке, используя C # и ключевое слово unchecked или проверяя переполнение для всего проекта (возможно как в проектах VB.NET, так и в C #). Если вы хотите, вы можете использовать ilmerge для объединения этой сборки с вашей основной сборкой.

1 голос
/ 26 августа 2014

После исследования того, что VB не дал нам ничего подобного unchecked и немного бушующего (c # dev сейчас делает vb), я реализовал решение, близкое к тому, которое опубликовал Ханс Пассант.Я потерпел неудачу в этом.Ужасная производительность.Это, безусловно, было связано с моей реализацией, а не с решением, опубликованным Хансом.Я мог бы вернуться и более внимательно скопировать его решение.

Однако я решил проблему другим способом.Сообщение с жалобой на отсутствие 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, который сталкивается с этим постом.

Cheers

1 голос
/ 13 мая 2014

Я также обнаружил, что RemoveIntegerChecks Свойство MsBuild влияет на / removeintchecks Свойство компилятора VB, которое не позволяет компилятору отправлять проверки во время выполнения:

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