Jeffrey Richter: Excerpt #3 from CLR via C#, Third Edition

Good morning everyone, Jeffrey Richter here.

Today I thought I’d share a section a section from Chapter 21, “Automatic Memory Management (Garbage Collection),”  of my new book with you. This section discusses a new .NET 4.0 class called ConditionalWeakTable and explains how to use it along with C#’s extension methods in order to associate an arbitrary piece of data with various objects. In effect, providing Object Local Storage.

> > > > >

Developers frequently want to associate a piece of data with another entity. For example, you can associate data with a thread or with an AppDomain. It is also possible to associate data with an individual object by using the System.Runtime.CompilerServices.ConditionalWeakTable<TKey,TValue> class, which looks like this:

public sealed class ConditionalWeakTable<TKey, TValue>

   where TKey : class where TValue : class {

   public ConditionalWeakTable();

   public void Add(TKey key, TValue value);

   public TValue GetValue(TKey key, CreateValueCallback<TKey, TValue> createValueCallback);

   public Boolean TryGetValue(TKey key, out TValue value);

   public TValue GetOrCreateValue(TKey key);

   public Boolean Remove(TKey key);

   public delegate TValue CreateValueCallback(TKey key); // Nested delegate definition

}

If you want to associate some arbitrary data with one or more objects, you would first create an instance of this class. Then, call the Add method passing in a reference to some object for the key parameter and the data you want to associate with the object in the value parameter. If you attempt to add a reference to the same object more than once, the Add method throws an ArgumentException; to change the value associated with an object, you must remove the key and then add it back in with the new value. Note that this class is thread-safe so multiple threads can use it concurrently, although this means that the performance of the class is not stellar; you should test the performance of this class to see how well it works for your scenario. Also, there is no good reason why TValue is constrained to class (only reference types). In the future, the CLR team might remove the constraint on TValue so that you can associate value type instances with an object without having to box the value types.

Of course, a table object internally stores a WeakReference to the object passed in as the key; this ensures that the table doesn’t forcibly keep the object alive. But what makes the ConditionalWeakTable class so special is that it guarantees that the value remains in memory as long as the object identified by the key is in memory. So this is more than a normal WeakReference because if it were, the value could be garbage collected even though the key object continued to live. The ConditionalWeakTable class could be used to implement the dependency property mechanism of Silverlight and Windows Presentation Foundation (WPF). It can also be used internally by dynamic languages to dynamically associate data with objects.

Here is some code that demonstrates the use of the ConditionalWeakTable class. It allows you to call the GCWatch extension method on any object passing in some String tag. Then it notifies you via the console window whenever that particular object gets garbage collected:

internal static class ConditionalWeakTableDemo {

   public static void Main() {

      Object o = new Object().GCWatch("My Object created at " + DateTime.Now);

      GC.Collect(); // We will not see the GC notification here

      GC.KeepAlive(o); // Make sure the object o refers to lives up to here

      o = null; // The object that o refers to can die now

      GC.Collect(); // We'll see the GC notification here

   }

}

internal static class GCWatcher {

   // NOTE: Be careful with Strings due to interning and MarshalByRefObject proxy objects

   private readonly static ConditionalWeakTable<Object, NotifyWhenGCd<String>> s_cwt =

      new ConditionalWeakTable<Object, NotifyWhenGCd<String>>();

   private sealed class NotifyWhenGCd<T> {

      private readonly T m_value;

      internal NotifyWhenGCd(T value) { m_value = value; }

      public override string ToString() { return m_value.ToString(); }

      ~NotifyWhenGCd() { Console.WriteLine("GC'd: " + m_value); }

   }

   public static T GCWatch<T>(this T @object, String tag) where T : class {

      s_cwt.Add(@object, new NotifyWhenGCd<String>(tag));

      return @object;

   }

}

Jeffrey Richter ( https://Wintellect.com )