The Conditional Weak Table: Enabling Dynamic Object Properties
The Dynamic Language Runtime allows you to implement a dynamic language (such as Ruby or Python) on top of the CLR. There are a lot of challenges to making everything work right. One particularly difficult aspect was enabling Ruby to attach arbitrary “properties” to instanced .NET managed objects at runtime.
If a Ruby developer sets an instance variable on a Ruby object everything can be handled by the Ruby compiler. But the Dynamic Language Runtime (DLR) offers the ability to use .NET types in your dynamic language. When a Ruby developer sets instance variables on .NET objects the objects are stored as keys in an internal table and the instance variables are stored as values.
For the first release, the DLR used a weak hashtable implementation to store keys and values. But in CLR v4, we did an interesting feature for the benefit of language compiler authors: the Conditional Weak Table. You can find the class ConditionalWeakTable<TKey, TValue> in the System.Runtime. CompilerServices namespace. It’s in CompilerServices because it’s not a general-purpose dictionary type: we intend for it to only be used by compiler writers.
The reason we needed to expose a new class in the runtime is that in some scenarios the keys and values can be pointing at each other. This causes problems in a weak hashtable because the GC can never free memory that is still referenced. Consider the following Ruby code, which sets instance variables (x and y) on a .NET object
# Allow Ruby to access .NET types
module Exception # Reopening CLR’s System.Exception
#This is equivalent to defining properties x and y in c#
e1 = System::Exception.new
e2 = System::Exception.new
# This causes a circular reference:
# e1.x points to e2 (and e2.y), and e2.y points to e1 (and e1.x)
e1.x = e2
e2.y = e1
The instance variables in this example create a circular reference between the keys and values. This is an issue in practice; hence the request for a CLR provided solution. Leaking a little memory doesn’t affect the correctness of the program, but it definitely isn’t an ideal situation.
You might be wondering how ConditionalWeakTable<TKey, TValue> is different from the Dictionary<TKey,TValue> class.
Unlike a Dictionary<TKey,TValue>, the keys in the weak table are weak references and the values are kept alive only if the corresponding object key is alive. This means that a key whose value has no references outside the conditional weak table is considered dead and the dictionary automatically removes the key/value entry after GC happens.
Also unlike Dictionary<TKey,TValue>, the weak table’s methods are thread-safe and requires no additional locking to be done by callers.
As stated in the beginning, the conditional weak table is not intended to be a general purpose collection. It does not expose the full public surface area of IDictionary. For example, it’s not possible to enumerate over the conditional weak table. If you need dictionary-like functionality in your code you should use the Dictionary<TKey,TValue> from the System.Collections.Generics namespace. But if you’re writing a .NET language of your own and need to expose the ability to attach properties to objects you should definitely look into the Conditional Weak Table.