Friday 9 March 2012

Getting an integer identity for an object on WP7

Today’s quirk was discovered while trying to get an object’s identity.

Jeffrey Richter in CLR via C# (version 2)mentions that you can call RuntimeHelpers static GetHashCode method in a box at the bottom of page 148 to get a unique ID for an object.

The documentation for the .NET framework 4 and earlier all say this about GetHashCode:

The RuntimeHelpers.GetHashCode method always calls the Object.GetHashCode method non-virtually, even if the object's type has overridden the Object.GetHashCode method. Therefore, using RuntimeHelpers.GetHashCode differs from calling GetHashCode directly on the object with the Object.GetHashCode method.

But, if you change to the Silverlight version of the documentation of GetHashCode that statement is missing, and if you actually build the code you’ll find that it just calls GetHashCode on the object itself, with whatever override is provided on that type (as these people found out)

Further investigation into GetHashCode all the way back to V2 of the framework looks unpromising as well:

The default implementation of the GetHashCode method does not guarantee unique return values for different objects. Furthermore, the .NET Framework does not guarantee the default implementation of the GetHashCode method, and the value it returns will be the same between different versions of the .NET Framework. Consequently, the default implementation of this method must not be used as a unique object identifier for hashing purposes.

There is some information here on other possible strategies on stackoverflow for getting an identity.

My intended use was for serialization, and the above thread points out the ObjectIDGenerator class which looks like exactly the functionality I’m looking for, but it doesn’t work on Silverlight!

GCHandle also doesn’t look like it will do the job, as most members are security critical, and the only value it gives out is a pinned address anyway, though there is a promising Narrowing operator, so casting to an IntPtr looks like it might work.

However, when I try to run it I get an exception:

Attempt to access the method failed: System.Runtime.InteropServices.GCHandle.set_Target(System.Object)

which I guess is expected as they’re marked with [SecurityCritical] attributes.

Despite all that, it does appear that object.ReferenceEquals will tell me if two objects are the same, and a promising approach appears to be

Dictionary<object, int> dict = new Dictionary<object, int>(10);

I initially created two objects and added them, and when querying dict.Count I got two items.

 

So, on my objects I created, I added an override of object.Equals to always return true.

Disappointingly, I now only have one object in my dictionary, as it obviously uses equality rather than identity.

 

However, hidden among all of the dictionary constructors is a constructor that takes an IEqualityComparer<T>, if only I could figure out how to create one.

 

Luckily, there’s an implementation here, the only change i made was to remove the call to RuntimeHelpers.GetHashCode() and use obj.GetHashCode() instead, as they do the same thing anyway on the phone.

 

After that, even with my operator= and GetHashCode overridden in my class to always return true and 0 respectively, my code as follows successfully labels two unique objects  in its Dictionary.

var datesTmp = new EphemeralState();
Dictionary<object, int> dict =
new Dictionary<object, int>
(10, ObjectReferenceEqualityComparerer<object>.Default);

dict[datesTmp] = 1;

var datesTmp2 = new EphemeralState();
dict[datesTmp2] = 2;

Debug.WriteLine(dict.Count);


 

So now we know how to build something equivalent to the ObjectIDGenerator class.  Find the value of an object key in the dictionary above, and if it doesn’t exist, generate a new id, and add it to the dictionary.

 

Back to work now.


 

No comments:

Post a Comment