Long time no post, and I'm just doing another because of a discussion on stackoverflow (in the comments) which got a bit off-topic.
So, like I mentioned in the last post, with .NET 4.0 we got ephemerons in the form of the ConditionalWeakTable class in the System.Runtime.CompilerServices namespace of mscorlib. This class is actually a collection of ephemerons hidden behind a dictionary-style API. The purpose of this class is to allow dynamic languages to 'attach' values to arbitrary objects, which also explains the choice of the namespace.
If you don't want to read through my last post I'll repeat the use-case of ephemerons and ConditionalWeakTable: the main problem is when you have a weak dictionary it could be possible that there is a reference chain from values in the weak dictionary to keys in the same weak dictionary. If those references form a cycle the "weakness" of the entries is effectively lost and the lifetime of the whole cycle is bound to the lifetime of the weak dictionary, which often is static and never collected at all.
Because these reference chains can be arbitrarily complex it is impossible to solve this cycle problem without help from the GC. Here is where ephemerons come into play, they effectively provide a way to specify conditional weak references: as long as the key is reachable, the value is also reachable. In other words, it is a way of saying "don't collect the value before the key".
So this problem is solved in .NET 4.0, great, but what if I want to roll my own weak dictionary because I don't like the behavior or API of ConditionalWeakTable? What if I want only a single ephemeron without the overhead of a collection? The implementation of the ConditionalWeakTable is based on a class called DependentHandle, however (unlike GCHandle and WeakReference) it is internal to mscorlib. Thats unfortunate, but as I said last time it is possible to replicate the implementation of DependentHandle (and thus ConditionalWeakTable) with some reflection trickery.
Of course this requires full trust and thus won't work on all .NET runtimes (silverlight, xbox, compact editions). Also it relies on implementation details of the .NET runtime so you need to check with every major update that your reflection code still works, fortunately this is easy since we'll only be relying on the names of four internal static methods, which can be easily checked to still exist.
People without access to reflection (or who don't want to use it) can obviously still use ConditionalWeakTable to create ephemerons. You can even chose between creating one table for each ephemeron you need, or sharing the table and just adding entries, since each entry is an ephemeron independant of the others in the table.
Implementation of DependentHandle
So, for those who are left, we can now finally get to the details on how to do the reflection magic to create your own ephemerons, DependentHandle and ConditionalWeakTable. We want maximum performance and minimum memory usage, so we don't just reflect into the constructor of DependentHandle: since it's a struct it would be boxed, and we would need to use reflection for any method call, which is not nice - but we can do better.
The trick is to know that DependentHandle is itself just a wrapper around the native runtime, much like GCHandle is for weak references. Internally it uses an IntPtr as a handle and static methods to talk with the native runtime. So if we could access those static methods we could roll our own DependentHandle. To get the best performance we'll use reflection to lookup the methods and then turn them into delegates. This allows the JIT to optimize calls to the reflected methods and is almost as fast as if you called the method directly.
To do the reflection magic we need a delegate signature which we can use to access the runtime implementation, as well as the function names. We can find these with from the .NET reference source, by using some decompiler, or just doing plain reflection to see what methods and fields are defined on ConditionalWeakTable and DependentHandle. Then we can use Delegate.CreateDelegate to create a delegate for the static method.
The four static methods we need are:
- void nInitialize(object primary, object secondary, out IntPtr dependentHandle)
- void nGetPrimary(IntPtr dependentHandle, out object primary)
- void nGetPrimaryAndSecondary(IntPtr dependentHandle, out object primary, out object secondary)
- void nFree(IntPtr dependentHandle)
Like we have WeakReference for GCHandle we wouldn't want to use DependentHandle directly. Instead we'd build a wrapper around it, like WeakReference is a wrapper around GCHandle. I've chosen to call it Ephemeron and provided a non-generic version (like WeakReference) and a generic version atop of it.
Note that this wrapper is tricky to get right because for some of the high-level functionality like changing keys we need multiple calls into the runtime, so we need to be careful for race conditions. My implementation is derived from WeakReference by looking at the .NET reference source and generalizing it to cover a key-value pair instead of a plain value. I added a lot comments to the implementation to document the race conditions.
So enough of the talk, I've added the implementation to my repository where I have the weak delegate implementation, here are the direct links: direct link to sourcecode and direct link to download.