Thursday 20 February 2014

Providing unique ID on managed object using ConditionalWeakTable (C#)

Introduction

​I was asked how to get a unique ID or something like this to distinguish managed objects and I answered object.GetHashCode() would do that. However, this answer is not perfect because sometimes GetHashCode() can return same values for different object instances. It is just for making hash codes for collections like hash maps so not perfect. 

Actually every managed object is represented by its reference which is unique and can be used for checking equity (e.g. Object.ReferenceEquals method) but sometimes it's difficult to use that because holding references block GC from collecting garbages. 

Luckily, .Net 4.0 introduced a new collection of ConditionalWeakTable<key, value> which doesn't affect on garbage collecting so that it's perfect to use for providing unique IDs on managed objects, by setting up an internal collection of ConditionalWeakTable<object, A_Counter_Class>. Its original purpose is to attach some extra data on alien objects but its characteristics allow us to track managed objects as well. 

Code

* I've just implemented and tested it by myself so please note that this code might not be perfect and can be (or should be) enhanced by yourself. ​

               class ObjectRecorder
              {
                      ConditionalWeakTable<object , object> _cwt = new ConditionalWeakTable<object , object>();

                      private static int _uniqueId = 0;

                      public void Add( object x )
                     {
                            try
                           {
                                   _cwt.Add (x, ( object)Interlocked .Increment( ref _uniqueId));
                           }
                            catch (System .ArgumentException) {}
                     }

                      public int GetValue( object x )
                     {
                            object id ;
                            if (_cwt .TryGetValue( x, out id))
                           {
                                   return (int )id;
                           }
                            return -1;
                     }

                      public ConditionalWeakTable <object, object> Cwt
                     {
                            get
                           {
                                   return _cwt ;
                           }
                     }
              }

The essential part of this class is _cwt as ConditionalWeakTable which manages a sort of map to store object reference in conjunction with its unique ID. If an attempt is made to add the same object instance again, it will do nothing. GetValue() method is to retrieve the ID connected to the given reference. 

Usage & Examples

               static void Main( string[] args )
              {
                      object a = new object();
                      object b = new object();
                      object bb = b;

                      List<int > lista = new List <int>();
                      List<int > listb = new List <int>();
                      List<int > listbb = listb;

                      object c = (object)0;
                      object d = (object)0;
                      object e = d;

                      ObjectRecorder x = new ObjectRecorder();
                      x.Add (a);
                      x.Add (a);
                      x.Add (b);
                      x.Add (bb);
                      x.Add (lista);
                      x.Add (listb);
                      x.Add (listbb);
                      x.Add (c);
                      x.Add (d);

                      Console.WriteLine ("a:" + x.GetValue (a). ToString());
                      Console.WriteLine ("a:" + x.GetValue (a). ToString());
                      Console.WriteLine ("b:" + x.GetValue (b). ToString());
                      Console.WriteLine ("bb:" + x.GetValue (bb). ToString());
                      Console.WriteLine ("lista:" + x.GetValue (lista). ToString());
                      Console.WriteLine ("listb:" + x.GetValue (listb). ToString());
                      Console.WriteLine ("listbb:" + x.GetValue (listbb). ToString());
                      Console.WriteLine ("c:" + x.GetValue (c). ToString());
                      Console.WriteLine ("d:" + x.GetValue (d). ToString());
                      Console.WriteLine ("e:" + x.GetValue (e). ToString());
                      Console.WriteLine ("x:" + x.GetValue (x). ToString());
               }

As per the expectation, it should be able to count "same references" only and not to count "same values". Here's the output. ​​​​


​​It definitely displayed the same IDs for the same references only so it actually works! 

Conclusion​​
  • Different ​GetHashCode() values mean different references, but the same values don't mean they are the same references as it's just a hash generation function which is not guaranteed to be unique per object. 
  • The real unique value is the reference itself but it doesn't come in handy for reference comparison in some cases as keeping references interferes garbage collection.
  • ConditionalWeakTable, introduced in .Net 4.0, is a good solution for this purpose as it doesn't impact on garbage collection whilst it can hold additional information per managed object. 
References
  • http://msdn.microsoft.com/en-us/library/dd287757(v=vs.100).aspx
  • http://stackoverflow.com/questions/750947/net-unique-object-identifier
Evernote helps you remember everything and get organized effortlessly. Download Evernote.

No comments: