Implementing Java ReferenceQueue and PhantomReference in C#

After reading Konrad Kokosa’s article on Java PhantomReference, I got reminded of a coding challenge a coworker gave me a few months ago, about implementing a ReferenceQueue in C#. The Java ReferenceQueue is a mechanism that allows the developer to know when an object has been garbage collected. One of the usages described by Konrad is the ability to cleanup native resources without keeping an object (and its graph of references) in memory. How hard can it be to reimplement it in .NET?

We know a way to be notified when an object is collected: its finalizer will be called. Let’s try to hack something together:

We defined a base class, PhantomReference, and the ReferenceQueue class itself. The PhantomReference class defines a finalizer that adds the instance to the associated ReferenceQueue. Queued instances are kept until dequeued by calling Poll.

From there, we can implement the PhantomReferenceFinalizer class (see Konrad’s article) on top of it:

The goal here is to have a way to clean the native resources held by LargeObject without keeping it in memory. Is it that simple? Well there’s a critical flaw in our implementation, as illustrated by this code:

What’s the problem? We’re actually tracking the lifecycle of our PhantomReference instead of our instance of LargeObject!

For this to work, we would need some kind of inverted WeakReference, where the object referenced (LargeObject) somehow keeps alive the object that holds the reference (PhantomeReference).

This unicorn actually exists in the .NET Framework. Its name is ConditionalWeakTable. It works like a key/value dictionary, except that a value is automatically removed when its key is garbage collected. With that tool in hand, we can finish writing our ReferenceQueue:

Then we can put everything together and verify that it works properly:

Whether this ReferenceQueue has any practical application is up to debate, but this was a pretty good excuse to talk about the ConditionalWeakTable. Who knows when you may need it?

But is the ConditionalWeakTable really mandatory? Actually, there is another way to implement the PhantomReference. If we think about it, we need two things:

  • A way to know whether our target object is still alive
  • A way to be notified when a garbage collection occurs, to know when to check

For the first condition, we can use a WeakReference to track the live status of an object. For the second, we already know of a GC notification system: the finalizer! All we need to do is checking whether the target object is still alive when the finalizer is called. If it is, then we re-register ourselves, to be notified at the next collection:

Of course, this is much less elegant than the ConditionalWeakTable. Still, this is a fun trick to play with.