0

I have a custom class which uses generics.

I need to use this class as the key of a dictionary as shown in the code example below:

I am able to hit the overridden Object.GetHashCode method, but i'm not sure how to proceed from there. Please help, Thanks.

    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
Charles Okwuagwu
  • 10,538
  • 16
  • 87
  • 157
  • 1
    I dont think ICompare will help with ContainsKey, it will return 1,0 or -1 indicating which is greater a or b. What/how is `ContainsKey` failing? what combo is it not detecting? – Ňɏssa Pøngjǣrdenlarp Jan 13 '15 at 14:49
  • @Plutonix it is not even hitting my IComparer method :( – Charles Okwuagwu Jan 13 '15 at 14:51
  • 2
    Yes. because not everything which can be used as a Key implements ICompare, so it has no idea it is there. Dictionary uses the hashcode to compare/find keys – Ňɏssa Pøngjǣrdenlarp Jan 13 '15 at 14:53
  • @Plutonix what do i need to implement then? – Charles Okwuagwu Jan 13 '15 at 14:54
  • I dont know what the problem is. Give an example of how/when ContainsKey is failing. – Ňɏssa Pøngjǣrdenlarp Jan 13 '15 at 14:56
  • @Plutonix what i mean by failing is that it is never calling my implemented IComparer , but your explanation says that i should drop IComparer and implement what exactly...? – Charles Okwuagwu Jan 13 '15 at 14:58
  • if `a = New Pair(Long, Int32)(5,5)` is a key in the dictionary, NET ought to report true with `myDict.ContainsKey(a)`. you do not have to do anything. HOWEVER, if you create a new `Pair(T, TT)` and try to find it, it will fail even with the same values because the NEW object has a different hashcode even with the same values. Is that the use-case? – Ňɏssa Pøngjǣrdenlarp Jan 13 '15 at 15:02
  • Sorry, I do not do chat. Just update the question with how/when ContainsKey is not finding the right object. – Ňɏssa Pøngjǣrdenlarp Jan 13 '15 at 15:06
  • 1
    You need to implement `IEqualityComparer(Of Pair(Of Long, Of Integer))` rather than `IComparer(...)`. – Jon Skeet Jan 13 '15 at 15:08
  • @JonSkeet Plutronix has pointed out a valuable lesson - i cannot use new to create a new object and expect to use it as a key in my dictionary – Charles Okwuagwu Jan 13 '15 at 15:10
  • have added the actual offending code... – Charles Okwuagwu Jan 13 '15 at 15:15
  • @CharlesO: I'm not sure what you mean. You can if you implement `IEqualityComparer` appropriately (and give that to the dictionary on construction) or implement `IEquatable` in the key class itself. – Jon Skeet Jan 13 '15 at 15:15
  • @JonSkeet let me try `IEquatable` in the key class, that looks like it should solve this. i have posted actual code in the edit above – Charles Okwuagwu Jan 13 '15 at 15:17
  • @JonSkeet no luck, still cannot hit the break point in my implemented IEquatable when I call ContainsKey for the dictionary. – Charles Okwuagwu Jan 13 '15 at 15:24
  • @CharlesO: You should be implementing the generic `IEquatable` interface. If you've done that, post a short but *complete* example demonstrating the problem. – Jon Skeet Jan 13 '15 at 15:25
  • @JonSkeet that's the full code included – Charles Okwuagwu Jan 13 '15 at 15:39
  • @Plutonix that's the full code included – Charles Okwuagwu Jan 13 '15 at 15:40
  • short of implementing a custom search and doing away with the dictionary `ContainsKey` i can't think of a way around this – Charles Okwuagwu Jan 13 '15 at 15:49
  • That's not a short but complete program, and you haven't overridden `GetHashCode`, which you should whenever you implement `IEquatable`. From the documentation: "If you implement `IEquatable`, you should also override the base class implementations of `Object.Equals(Object)` and `GetHashCode` so that their behavior is consistent with that of the `IEquatable.Equals` method." – Jon Skeet Jan 13 '15 at 15:57
  • LOL @ the down-vote... seriously. I have an actual problem i'm trying to figure out and the best you can do is down-vote me? To whoever did that...Not cool at all ... i've tried out every implementation I can think of, even guided by the comments here... – Charles Okwuagwu Jan 13 '15 at 16:29
  • No, chat isn't convenient for me right now. If you implement `Equals(object)`, `Equals(T)` and `GetHashCode` appropriately, it *will* work. – Jon Skeet Jan 13 '15 at 16:34
  • Well there's no indication that you've tried to override `GetHashCode` appropriately, for one thing - and you still haven't produced a short but complete program demonstrating the problem... – Jon Skeet Jan 13 '15 at 16:34

1 Answers1

1

This implements IEquatable and overrides GetHashCode and 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

Then, the methods:

' 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

Testing:

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)

Result:

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

The tests using c are actually false. The object 'c' was never added to the Dictionary, and 'c' is a different object than a. But the overrides used simply tests the objects based on the 2 values which are the same.

I do not think I would do this, because a<>c. Instead, maybe use a collection class of some sort and avoid redefining Equals. The exact implementation would depend on whats in the collection. This would be a last resort, for me.

See Also:

Community
  • 1
  • 1
Ňɏssa Pøngjǣrdenlarp
  • 38,411
  • 12
  • 59
  • 178
  • I see you have done away with Public Function ContainsItem(id As Long, code As Integer) As Boolean, which like you have pointed out will suffer from the same issue where c is not in the dictionary and c=a would be wrongly implied – Charles Okwuagwu Jan 13 '15 at 17:16
  • I did not notice `ContainsItem` - it is just a wrapper for ContainsKey though. You might need to modify/improve GetHashCode, it would be easier if Pair was not generic - for the Dictionary they all have to be (Long, Int) so whats the point *in this case*? Seems fairly specialized. – Ňɏssa Pøngjǣrdenlarp Jan 13 '15 at 17:28
  • In this case dictionary key **is** indeed of type Pair(of long, int32), but there are several other key types for example Pair(of string, int32) hence I need Pair(of T1,T2) – Charles Okwuagwu Jan 13 '15 at 17:37