Collection with composite key

J

Jeff Mason

I'm trying to define a collection which inherits from DictionaryBase. This
collection requires a composite key (comprised of two string values) and the value
associated with the key is an integer.

I thought this would be easy.

I defined a "key" class (called 'AccessKey') which exposed two string properties and
implemented a GetHashCode which simply returned the GetHashCode of their
concatenation.

I defined an add method in my derived collection class as:

Public Sub Add(ByVal Key As AccessKey, ByVal Value As Integer)
MyBase.Dictionary.Add(Key, Value)
End Sub

This appeared to work fine. When I ran code such as:

myCollection.Add(new AccessKey("string1","string2"), 1)
myCollection.Add(new AccessKey("string3","string4"), 99)

in debug mode it appeared that the collection contained the right values with my
AccessClass as a key.

But try as I might, I couldn't get the thing to *return* the correct value for a
given key. I implemented an Item property as:

Default ReadOnly Property Item(ByVal key As AccessKey) As Integer
Get
Return CType(Dictionary.Item(key), Integer)
End Get
End Property

but this always returns a 0 no matter what key value I use. Thus:

dim somevalue as integer = myCollection(new AccessKey("string3","string4"))

returns a 0 instead of 99.

I'm doing something stupid, but what?

I can get around this by throwing out the concept of a key class and just
concatenating the two strings together and using the resultant string as a key. I've
tried this and it works fine, but I really want to understand what I am missing when
I attempt to define and use a composite key class.
 
M

Michael Keating

Jeff Mason said:
I'm trying to define a collection which inherits from DictionaryBase. This
collection requires a composite key (comprised of two string values) and the value
associated with the key is an integer.

I thought this would be easy.

I defined a "key" class (called 'AccessKey') which exposed two string properties and
implemented a GetHashCode which simply returned the GetHashCode of their
concatenation.

I defined an add method in my derived collection class as:

Public Sub Add(ByVal Key As AccessKey, ByVal Value As Integer)
MyBase.Dictionary.Add(Key, Value)
End Sub

This appeared to work fine. When I ran code such as:

myCollection.Add(new AccessKey("string1","string2"), 1)
myCollection.Add(new AccessKey("string3","string4"), 99)

in debug mode it appeared that the collection contained the right values with my
AccessClass as a key.

But try as I might, I couldn't get the thing to *return* the correct value for a
given key. I implemented an Item property as:

Default ReadOnly Property Item(ByVal key As AccessKey) As Integer
Get
Return CType(Dictionary.Item(key), Integer)
End Get
End Property

but this always returns a 0 no matter what key value I use. Thus:

dim somevalue as integer = myCollection(new AccessKey("string3","string4"))

returns a 0 instead of 99.

I'm doing something stupid, but what?

I can get around this by throwing out the concept of a key class and just
concatenating the two strings together and using the resultant string as a key. I've
tried this and it works fine, but I really want to understand what I am missing when
I attempt to define and use a composite key class.

Hi,

I'm still rather new to .Net myself, so don't take this as gospel by any
means, but ...

It appears to me that you are using the actual instances of the AccessKey
class as the key to the collection. Then as you are passing the AccessKey
ByVal, you are creating a copy, so another instance, in the property get ..
also using the New keyword you are creating another instance. If that is so,
then each instance is different and so a different key.

Although, if that were true, then you should be getting errors thrown
instead of wrong results .. but anyway .. a suggestion ..

I think that if you create a "GetKey" method of AccessKey which returns a
string ( the concatenation of the two string properties ) and use that to
access the collection .. i.e.

dim somevalue as integer = myCollection(new
AccessKey("string3","string4").GetKey)

then the results should be as you want.

I havent tested this, and my reasoning may be off, but I think it worth a
try.

HTH

MFK.
 
J

Jay B. Harlow [MVP - Outlook]

Michael,
It appears to me that you are using the actual instances of the AccessKey
class as the key to the collection. Then as you are passing the AccessKey
ByVal, you are creating a copy, so another instance, in the property get ...
also using the New keyword you are creating another instance. If that is so,
then each instance is different and so a different key.

No Jeff indicated that AccessKey is a Class, which means that it is a
reference type, no copies of the actual class are being created.

Hope this helps
Jay
 
J

Jay B. Harlow [MVP - Outlook]

Jeff,
In order for AccessKey to be a good key for a Hashtable (DictionaryBase) you
need to override both GetHashCode & Equals!

Something like:

Public Class AccessKey

Private ReadOnly m_key1, m_key2 As String

Public Sub New(ByVal key1 As String, ByVal key2 As String)
m_key1 = key1
m_key2 = key2
End Sub

Public Overrides Function GetHashCode() As Integer
Return m_key1.GetHashCode() Xor m_key2.GetHashCode()
End Function

Public Overloads Overrides Function Equals(ByVal obj As Object) As
Boolean
Dim other As AccessKey = DirectCast(obj, AccessKey)
Return m_key1 = other.m_key1 AndAlso m_key2 = other.m_key2
End Function

End Class

Remember that a Hashtable will store entries by the hash code, then it uses
the Equals function to find an exact match, as two objects may be different,
however they may return the same hash code.

Hope this helps
Jay
 
M

Michael Keating

Jay B. Harlow said:
Michael,

No Jeff indicated that AccessKey is a Class, which means that it is a
reference type, no copies of the actual class are being created.

Hope this helps
Jay

Damn! .. this .Net stuff .. more reading needed <g>

Thanks

MFK
 
J

Jeff Mason

Jeff,
In order for AccessKey to be a good key for a Hashtable (DictionaryBase) you
need to override both GetHashCode & Equals!

Thank you! That was it. Once I provided the overridden Equals method everything
worked perfectly.

....
Remember that a Hashtable will store entries by the hash code, then it uses
the Equals function to find an exact match, as two objects may be different,
however they may return the same hash code.
Yes. On another note, I did provide an override for GetHashCode. I did so by simply
returning the GetHashCode value of the two strings concatenated together, as:

Return (m_key1 & m_key2).GetHashCode

but I notice your override was the XOR of each individual string's GetHashCode value.
Aside from the fact that I'm sure your method is much faster, is there any other
advantage to the XOR method?
Hope this helps

You helped a lot. Thank you.
 
J

Jeff Mason

Just to follow up my own post, I wonder if there are any other methods that should be
routinely overriden when deriving from one of the base collection classes.

For example, the problem I originally posted was for just a "lookup" collection. It
would contain a fairly large number of elements, but the only requirement is to
furnish a value based on a lookup key. No requirement exists to iterate over the
collection or for it to be ordered in any way - the lookups are always random.

I can anticipate other uses for other collections, though. Would I need to do
something special to provide a for...each capability for these composite key
collections, for example?

Thanks for any insight anyone can provide...
 
J

Jay B. Harlow [MVP - Outlook]

Jeff,
but I notice your override was the XOR of each individual string's GetHashCode value.
Aside from the fact that I'm sure your method is much faster, is there any other
advantage to the XOR method?
Using Xor is a commonly recommended method. The biggest reason I use it, is
because it does not tie you specifically to Strings. Consider the AccessKey
class where key1 & key2 are Object or Integer or a mix, instead of both
being String.

For example AccessKey could be 2 integers, to represent indexes into a
sparse array of elements (10 out of 1 million indexes actually have values).

Hope this helps
Jay
 
J

Jay B. Harlow [MVP - Outlook]

Jeff,
For each is provided by the collection itself.

Hashtables requires GetHashCode & Equals to be overridden in the contained
class

ArrayLists only require Equals to be overridden in the contained class.
However I would still override GetHashCode if possible.

Overriding ToString is useful in contained classes, if you are going to bind
the collection to a ListBox or ComboBox. However the collection itself
doesn't need it.

Otherwise you need to go on a class by class basis on what it expects & what
it needs...

Hope this helps
Jay
 
J

Jeff Mason

For each is provided by the collection itself.
As I understand it, the gotcha here though is that the for...each returns a
DictionaryEntry structure. I tried this and the 'key' member of the returned
DictionaryEntry was indeed my key class, and 'value' was the associated value.

Thanks very much, Jay. I think I have handle on this now, thanks to your help.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top