2

I'm trying to use a .Net library with asynchronous callbacks generated by a number of instances of the API class, and once in my event handler I need to find which instance this particular event came from, as well as all my own information about its context.

The first part is easy as the callback's first argument will be the very object that's sending the notification, and just needs casting. But now I'm stuck looking for a way to associate it with my own data, as that object has very few public properties to identify an instance with.

At the moment, I declared...

Dictionary<TheDotNetClass,MyClass> 

..and it works seemingly OK, but my suspicion is that it's simply using the generic Object.getHashCode/compare and it's probably far from safe, might change over time, would lead to way too much collision, etc.

Yet I cannot think of any alternative that would help me connect the generic object I'm getting with all the metadata that I have associated with that API instance on my side.

Am I overlooking something? Is what I'm doing safe enough to scale? Thanks!

Blorgbeard
  • 101,031
  • 48
  • 228
  • 272
Vini Bono
  • 85
  • 8

2 Answers2

2

The default Equals and GetHashCode are based on reference equality. If the object doesn't override Equals and GetHashCode (which you can verify with a decompiler like dotPeek), then each object instance will be a unique dictionary key, so yes, you can use a Dictionary to attach extra data to each instance. This is a fairly common scenario, and it's completely supported -- you're using it as designed.

I wouldn't worry about hash collisions. The BCL team are smart; they designed the base GetHashCode to be used as a hash key, so collisions should be minimal.

Edit: Here's a code snippet that shows the hash code isn't based on the object's address. After compaction, b's address will change, but note that its hash code does not.

var a = new object();
var originalHashCodeA = a.GetHashCode();
for (var i = 0; i < 10000; ++i)
    new object();
var b = new object();
var originalHashCodeB = b.GetHashCode();

// Synchronous garbage collection (see http://stackoverflow.com/q/748777/87399)
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

Console.WriteLine(a.GetHashCode() == originalHashCodeA);
Console.WriteLine(b.GetHashCode() == originalHashCodeB);

Note that the output is True, True. Neither object's hash code changed after the garbage collection and compaction. So yes, the default GetHashCode is safe across compactions.

Joe White
  • 94,807
  • 60
  • 220
  • 330
  • Great. Thanks! So I take it the GC is not allowed to change the addresses from under my nose, or other nasty side effects? That's reassuring. – Vini Bono May 06 '14 at 06:59
  • It's *absolutely* free to change the addresses (it's a compacting garbage collector). But a compaction won't change an instance's hash code. I added example code you can use to see this in action. – Joe White May 06 '14 at 12:32
  • Thanks Joe! I should probably have said reference rather than address, but I get it now, and this is good news indeed! – Vini Bono May 06 '14 at 21:09
0

That method is fine. The only time two hash codes would match (assuming the class is implemented properly) is when Object.Equals would return true.

If that function would return true, then I would imagine you want the "collision" to happen because they are effectively the same object. Note that when you are using a custom class, it is recommended that you override .Equals and .GetHashCode for this very purpose.

BradleyDotNET
  • 60,462
  • 10
  • 96
  • 117
  • "The only time two hash codes would match (assuming the class is implemented properly) is when Object.Equals would return true" - not true. There are only 2^32 possible hash-codes, and there can certainly be more unique objects in memory than that. The **opposite** is true - when Object.Equals returns true, the hash-codes **must** match. – Blorgbeard Jul 30 '14 at 01:35