Как вы используете пользовательский тип для ключа словаря?

У меня есть собственный класс, который использует дженерики.

Мне нужно использовать этот класс в качестве ключа словаря, как показано в примере кода ниже:

Я могу использовать переопределенный метод Object.GetHashCode, но я не уверен, как действовать дальше. Пожалуйста, помогите, спасибо.

    Module Module2
    Dim myStore As New Dictionary(Of Pair(Of Long, Integer), String)

    Public Function ContainsItem(id As Long, code As Integer) As Boolean
        Return myStore.ContainsKey(New Pair(Of Long, Integer)(id, code))
    End Function

    Public Class Pair(Of T1, T2)
        Implements IEquatable(Of Pair(Of T1, T2))

        Private v1 As T1
        Private v2 As T2

        Public Sub New(ByVal v1 As T1, ByVal v2 As T2)
            Me.v1 = v1
            Me.v2 = v2
        End Sub

        Public Function first() As T1
            Return v1
        End Function

        Public Function second() As T2
            Return v2
        End Function

        Public Overrides Function GetHashCode() As Integer
            'i hit this break point, but ... 
            'how do i compute an integer hashcode from a long and an Integer?
            Return MyBase.GetHashCode()
        End Function

        Public Overrides Function Equals(obj As Object) As Boolean
            Return MyBase.Equals(obj)
        End Function

        Public Function Equals1(other As Pair(Of T1, T2)) As Boolean Implements IEquatable(Of Pair(Of T1, T2)).Equals
            'just as a test, but the code never gets here.
            Return True
        End Function
    End Class

    Public Sub TestCase()
        Dim a = New Pair(Of Long, Integer)(10, 10)
        myStore.Add(a, "Item 1")

        Dim b = ContainsItem(10, 10)
        'b is always false 
    End Sub
End Module

person Charles Okwuagwu    schedule 13.01.2015    source источник
comment
Я не думаю, что ICompare поможет с ContainsKey, он вернет 1,0 или -1, указывая, что больше a или b. Что/как ContainsKey терпит неудачу? какую комбинацию не видит?   -  person Ňɏssa Pøngjǣrdenlarp    schedule 13.01.2015
comment
@Plutonix даже не попадает в мой метод IComparer :(   -  person Charles Okwuagwu    schedule 13.01.2015
comment
Да. потому что не все, что можно использовать в качестве ключа, реализует ICompare, поэтому он понятия не имеет, что он там есть. Словарь использует хэш-код для сравнения/поиска ключей   -  person Ňɏssa Pøngjǣrdenlarp    schedule 13.01.2015
comment
@Plutonix, что мне тогда нужно реализовать?   -  person Charles Okwuagwu    schedule 13.01.2015
comment
Я не знаю, в чем проблема. Приведите пример того, как/когда не работает ConstainKey.   -  person Ňɏssa Pøngjǣrdenlarp    schedule 13.01.2015
comment
@Plutonix под ошибкой я подразумеваю то, что он никогда не вызывает мой реализованный IComparer , но ваше объяснение говорит, что я должен отказаться от IComparer и реализовать что именно ...?   -  person Charles Okwuagwu    schedule 13.01.2015
comment
если a = New Pair(Long, Int32)(5,5) является ключом в словаре, NET должен сообщать об истине с помощью myDict.ContainsKey(a). вам не нужно ничего делать. ОДНАКО, если вы создадите новый Pair(T, TT) и попытаетесь найти его, он потерпит неудачу даже с теми же значениями, потому что НОВЫЙ объект имеет другой хэш-код даже с теми же значениями. Это вариант использования?   -  person Ňɏssa Pøngjǣrdenlarp    schedule 13.01.2015
comment
Извините, я не веду чат. Просто обновите вопрос, указав, как/когда ContainsKey не находит нужный объект.   -  person Ňɏssa Pøngjǣrdenlarp    schedule 13.01.2015
comment
Вам нужно реализовать IEqualityComparer(Of Pair(Of Long, Of Integer)), а не IComparer(...).   -  person Jon Skeet    schedule 13.01.2015
comment
@JonSkeet Plutronix преподал ценный урок: я не могу использовать new для создания нового объекта и рассчитываю использовать его в качестве ключа в моем словаре.   -  person Charles Okwuagwu    schedule 13.01.2015
comment
добавили фактический оскорбительный код...   -  person Charles Okwuagwu    schedule 13.01.2015
comment
@CharlesO: я не понимаю, что вы имеете в виду. Вы можете, если вы реализуете IEqualityComparer соответствующим образом (и передадите это словарю при построении) или реализуете IEquatable в самом ключевом классе.   -  person Jon Skeet    schedule 13.01.2015
comment
@JonSkeet позвольте мне попробовать IEquatable в ключевом классе, похоже, это должно решить эту проблему. я разместил фактический код в редактировании выше   -  person Charles Okwuagwu    schedule 13.01.2015
comment
@JonSkeet не повезло, я все еще не могу попасть в точку останова в моем реализованном IEquatable, когда я вызываю ContainsKey для словаря.   -  person Charles Okwuagwu    schedule 13.01.2015
comment
@CharlesO: вы должны реализовать общий интерфейс IEquatable. Если вы это сделали, опубликуйте короткий, но полный пример, демонстрирующий проблему.   -  person Jon Skeet    schedule 13.01.2015
comment
@JonSkeet, это полный код   -  person Charles Okwuagwu    schedule 13.01.2015
comment
@Plutonix включает полный код   -  person Charles Okwuagwu    schedule 13.01.2015
comment
если не считать реализации пользовательского поиска и отказа от словаря ContainsKey, я не могу придумать, как это обойти   -  person Charles Okwuagwu    schedule 13.01.2015
comment
Это не короткая, но полная программа, и вы не переопределили GetHashCode, что вы должны делать всякий раз, когда реализуете IEquatable<T>. Из документации: если вы реализуете IEquatable<T>, вы также должны переопределить реализации базового класса Object.Equals(Object) и GetHashCode, чтобы их поведение соответствовало поведению метода IEquatable<T>.Equals.   -  person Jon Skeet    schedule 13.01.2015
comment
LOL @ отрицательный голос ... серьезно. У меня есть реальная проблема, которую я пытаюсь решить, и лучшее, что вы можете сделать, это проголосовать за меня? Тому, кто это сделал... Совсем не круто... я перепробовал все реализации, которые только мог придумать, даже руководствуясь комментариями здесь...   -  person Charles Okwuagwu    schedule 13.01.2015
comment
Нет, чат мне сейчас не удобен. Если вы правильно реализуете Equals(object), Equals(T) и GetHashCode, это будет работать.   -  person Jon Skeet    schedule 13.01.2015
comment
Ну, нет никаких признаков того, что вы пытались переопределить GetHashCode должным образом, во-первых, и вы до сих пор не создали короткую, но полную программу, демонстрирующую проблему...   -  person Jon Skeet    schedule 13.01.2015


Ответы (1)


Это реализует IEquatable и переопределяет GetHashCode и Equals:

' an assumption about Pair(Of... :
Public Class Pair(Of T, TT)
    Implements IEquatable(Of Pair(Of T, TT))

    Public Property ValueT As T
    Public Property ValueTT As TT

Затем методы:

' basic Equals for this Type
Public Overrides Function Equals(obj As Object) As Boolean
    If obj.GetType Is GetType(Pair(Of Long, Integer)) Then
        Return Equals1(CType(obj, Pair(Of T, TT)))
    Else
        Return False
    End If
End Function

' used by the Dictionary 
Public Function Equals1(obj As Pair(Of T, 
                 TT)) As Boolean Implements IEquatable(Of Pair(Of T, TT)).Equals
    ' the other thing is Something Else
    If obj.GetType <> GetType(Pair(Of Long, Integer)) Then
        Return False
    End If

    'prefer T over TT, testing first
    If Integer.Equals(obj.ValueT, ValueT) = False Then
        Return False
    End If

    'T is equal, what about TT:
    Return Long.Equals(obj.ValueTT, ValueTT)
End Function

 ' dictionary will use the hashcode for ContainsKey, Add
Public Overrides Function GetHashCode() As Integer
    ' https://stackoverflow.com/a/371348/1070452
    ' marc gravell:
    Dim hash As Integer = 13
    hash = (hash * 7) + ValueT.GetHashCode()
    hash = (hash * 7) + ValueTT.GetHashCode()

    Return hash

    ''msdn (non generic value types):
    'Dim hCode As Long = ValueT Xor ValueTT
    'Return hCode.GetHashCode()
End Function

Тестирование:

Dim a = New Pair(Of Long, Integer)(10, 10)
Dim b = New Pair(Of Long, Integer)(5, 5)
' different object, same values:
Dim c = New Pair(Of Long, Integer)(10, 10)

Dim mydict As New Dictionary(Of Pair(Of Long, Int32), String)
mydict.Add(a, "ziggy")
mydict.Add(b, "zoey")

Console.WriteLine("a==b? {0}", a.Equals(b).ToString)
Console.WriteLine("a==c? {0}", a.Equals(c).ToString)
Console.WriteLine("b==c? {0}", b.Equals(c).ToString)

Console.WriteLine("Contains a? {0}", mydict.ContainsKey(a).ToString)
Console.WriteLine("Contains b? {0}", mydict.ContainsKey(b).ToString)
' since the c OBJECT is not in the collection, it SHOULD report false
 ' but since the values are, and thats all that seems to matter:
Console.WriteLine("Contains c? {0}", mydict.ContainsKey(c).ToString)

Результат:

a==b? False
a==c? True
b==c? False
Contains a? True
Contains b? True
Contains c? True

Тесты с использованием c на самом деле ложны. Объект «с» никогда не добавлялся в словарь, а «с» — это другой объект, чем a. Но используемые переопределения просто проверяют объекты на основе двух значений, которые одинаковы.

Не думаю, что сделал бы это, потому что a<>c. Вместо этого, возможно, используйте какой-то класс коллекции и избегайте переопределения Equals. Точная реализация будет зависеть от того, что находится в коллекции. Для меня это будет крайняя мера.

Смотрите также:

person Ňɏssa Pøngjǣrdenlarp    schedule 13.01.2015
comment
Спасибо за объяснения - person Charles Okwuagwu; 13.01.2015
comment
Я вижу, вы покончили с публичной функцией ContainsItem (id As Long, code As Integer) As Boolean, которая, как вы указали, будет страдать от той же проблемы, когда c нет в словаре, а c = a будет ошибочно подразумеваться - person Charles Okwuagwu; 13.01.2015
comment
Я не заметил ContainsItem - это просто обертка для ContainsKey. Возможно, вам потребуется изменить/улучшить GetHashCode, было бы проще, если бы Pair не был универсальным - для Словаря все они должны быть (Long, Int), так какой смысл в этом случае? Кажется довольно специализированным. - person Ňɏssa Pøngjǣrdenlarp; 13.01.2015
comment
В этом случае ключ словаря имеет действительно тип Pair(of long, int32), но есть несколько других типов ключей, например Pair(string, int32), поэтому мне нужна Pair(of T1,T2) - person Charles Okwuagwu; 13.01.2015