Dictionary.ContainsKey() confusion

J

Jeff Dege

I'm trying to do something fairly simple, and I'm generating nothing but
confusion.

Does Dictionary.ContainsKey() compare pointers, or values? I have
defined two classes, FooId, and FooData. With these, I've declared a
fooDictionary Dictionary collection. I then run a database query, and
loop on a SqlDataReader object.

In each loop, I construct a new FooId object and a new FooData object.

I then check fooDictionary.ContainsKey(fooId). This returns true, when
there is already a FooId object with the same values as the new FooId
object I've just created. If so, I create a new FooData object, and add
it into the collection.

My initial problem was that I wanted to walk down the collection, calling
a method on every FooData object, when that method would modify the member
fields of the FooData object in the collection. And I simply couldn't
find a way for that to work.

If I called the method on the object in place, it would seem to work, but
the object in the collection would not actually be modified. It's like if
I did a foreach(KeyValuePair<> kvp in ), kvp.Value was giving me a copy of
the object in the collection, rather than a reference to the object. And
if I tried to replace the object, I'd get a "collection modified"
exception thrown.

So I started to put together a simple example, showing what was going on.
But I couldn't do it. First, with my simple example, the
foreach(KeyValuePair) worked - and did modify the object in the
collection. But calling Dictionary.ContainsKey() on a newly constructed
object did not - FooId objects with identical values would never match,
and I'd end up with a Dictionary containing multiple keys with identical
member fields.

I don't understand it. Why would a Dictionary.ContainsKey() seem to be
doing value comparisons with the classes I was building from the
SqlDataReader, but be doing pointer comparisons with the simple classes I
was building for my demo?

And why would KeyValuePair.Value return a copy of the object in the
collection, when that object was constructed from the SqlDataReader, but
return a reference to the object in the collection, when it was
constructed from my simple demo data?

It's purely a befuddlement.

--
The police are the public and the public are the police; the police being
only members of the public who are paid to give full time attention to
duties which are incumbent on every citizen in the interests of community
welfare and existence.
-Sir Robert Peel.
 
J

Jon Skeet [C# MVP]

Jeff Dege said:
I'm trying to do something fairly simple, and I'm generating nothing but
confusion.

Does Dictionary.ContainsKey() compare pointers, or values?

It compares hash codes to start with, then compares the key you present
it against keys that have the same hash code using the Equals method.
My initial problem was that I wanted to walk down the collection, calling
a method on every FooData object, when that method would modify the member
fields of the FooData object in the collection. And I simply couldn't
find a way for that to work.

If I called the method on the object in place, it would seem to work, but
the object in the collection would not actually be modified. It's like if
I did a foreach(KeyValuePair<> kvp in ), kvp.Value was giving me a copy of
the object in the collection, rather than a reference to the object. And
if I tried to replace the object, I'd get a "collection modified"
exception thrown.

That would certainly be the case if FooData were a struct rather than a
class. Is that the case by any chance?
So I started to put together a simple example, showing what was going on.
But I couldn't do it. First, with my simple example, the
foreach(KeyValuePair) worked - and did modify the object in the
collection. But calling Dictionary.ContainsKey() on a newly constructed
object did not - FooId objects with identical values would never match,
and I'd end up with a Dictionary containing multiple keys with identical
member fields.

I don't understand it. Why would a Dictionary.ContainsKey() seem to be
doing value comparisons with the classes I was building from the
SqlDataReader, but be doing pointer comparisons with the simple classes I
was building for my demo?

And why would KeyValuePair.Value return a copy of the object in the
collection, when that object was constructed from the SqlDataReader, but
return a reference to the object in the collection, when it was
constructed from my simple demo data?

Both of these problems sound like they would be the case if you were
using value types rather than classes. Value types have a simple
implementation of Equals to start with, whereas for reference types
Equals just compares references (unless it's overridden).
 
P

Peter Duniho

[...]
I don't understand it. Why would a Dictionary.ContainsKey() seem to be
doing value comparisons with the classes I was building from the
SqlDataReader, but be doing pointer comparisons with the simple classes I
was building for my demo?

[...]
It's purely a befuddlement.

Did you read the documentation for the Dictionary<> generic class?
http://msdn2.microsoft.com/en-us/library/xfhwa508.aspx

Here's your answer, from that page:

Dictionary requires an equality implementation to determine
whether keys are equal. You can specify an implementation of
the IEqualityComparer generic interface by using a constructor
that accepts a comparer parameter; if you do not specify an
implementation, the default generic equality comparer
EqualityComparer.Default is used. If type TKey implements the
System.IEquatable generic interface, the default equality
comparer uses that implementation.

You can either provide your own IEqualityComparer, or if it makes sense
for your classes to always be considered equal in other situations,
implement the Equals() method for your classes, and that will be used by
default.

Pete
 
J

Jeff Dege

[...]
I don't understand it. Why would a Dictionary.ContainsKey() seem to be
doing value comparisons with the classes I was building from the
SqlDataReader, but be doing pointer comparisons with the simple classes I
was building for my demo?

[...]
It's purely a befuddlement.

Did you read the documentation for the Dictionary<> generic class?
http://msdn2.microsoft.com/en-us/library/xfhwa508.aspx

Here's your answer, from that page:

That doesn't explain why the two implementations behave differently,
particularly when it comes to the modification of objects within the
collection.

--
Security is mostly a superstition. It does not exist in nature, nor
do the children of men as a whole experience it. Avoiding danger in
the end is no safer in the long run than outright exposure. Life is a
daring adventure or nothing.
- Helen Keller
 
J

Jon Skeet [C# MVP]

Jeff Dege said:
That doesn't explain why the two implementations behave differently,
particularly when it comes to the modification of objects within the
collection.

For that you need to understand the difference between value types and
reference types.
 
P

Peter Duniho

That doesn't explain why the two implementations behave differently,
particularly when it comes to the modification of objects within the
collection.

IMHO, it does, assuming you described your problem accurately when you
wrote "Why would a Dictionary.ContainsKey() seem to be doing value
comparisons with the classes I was building from the SqlDataReader, but be
doing pointer comparisons with the simple classes I was building for my
demo?"

Specifically, you claim that in both cases, you are using classes and not
structs (as Jon has inferred, probably correctly but for now let's assume
he's incorrect). Assuming that, the behavior you're seeing, at least with
respect to finding the keys, is entirely explained by the passage I quoted.

In particular, it's likely that the SQL-based classes do implement
Equals() in a relevant way, while your test class used as the key does
not. When Equals() isn't implemented, you get a reference comparison,
which of course is never true if you are creating new instances for the
purpose of retrieving something from the Dictionary<>.

The easiest explanation for the other behavior you're seeing is that, as
Jon suggests, your class isn't really a class but instead is a value type
(struct). If that's the case, then retrieving the value from the
Dictionary<> gives you a copy of the value stored in the dictionary, and
operating on the value doesn't change the original. If this is the case,
then I'd say it's a good example of why making mutable value types isn't a
good idea (because the copy behavior of value types introduces exactly
this sort of bug).

I believe that because structs do inherit from the Object class (odd but
true :) ), you can implement Equals() in a struct and resolve the other
issue you're having as well.

Pete
 
J

Jon Skeet [C# MVP]

On Jun 28, 7:48 am, "Peter Duniho" <[email protected]>
wrote:

I believe that because structs do inherit from the Object class (odd but
true :) )

It gets even odder when you start looking at the CLI spec. Value types
(in their unboxed form) don't *inherit* from anything but they do
*derive* from System.ValueType (or System.Enum).
From the annotated spec: "The terms 'is derived from' and 'inherits
from' are usually interchangeable; in the CLI, however, they are not
interchangeable for value types. (Note: 'inherits from' and 'is a
subclass of' are equivalent terms; also 'derived from' and 'extends'
are equivalent.)"

So that's as clear as mud then :)

Jon
 
P

Peter Duniho

[...]
From the annotated spec: "The terms 'is derived from' and 'inherits
from' are usually interchangeable; in the CLI, however, they are not
interchangeable for value types. (Note: 'inherits from' and 'is a
subclass of' are equivalent terms; also 'derived from' and 'extends'
are equivalent.)"

lol...my head is spinning. Don't do that! :p
 

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