Переопределение GetHashCode в VB без поддержки проверенных / непроверенных ключевых слов?

поэтому я пытаюсь выяснить, как правильно переопределить 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 для granular checked/unchecked поддержка, я не могу обойти это.

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

какой будет "безопасная" версия Джона GetHashCode версия для VB и Int32? Или у .NET 4.0 есть checked/unchecked в нем где-то, что я не нахожу очень легко на MSDN?


EDIT:
По связанному вопросу SO, один из нелюбимые ответы в самом низу предоставил квази-решение. Я говорю "квази", потому что мне так кажется....мошенничество. Но нищие не выбирают, верно?

переведенный с C# на более читаемый VB и выровненный с объектом, описанным выше (имя, значение, тип), мы получаем:

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


используйте 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)

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

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

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

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

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


улучшена ответа переопределение 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

существует обрезка после каждого умножения. И литерал определяется явно, поскольку оператор And с целочисленным аргументом не обнуляет верхние байты.


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

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

после исследования, что 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, которые сталкиваются с этим сообщением.

Ура