I ran into this very interesting blog post and I decided to see if I could implement this on my own, without requiring any runtime support. This turned out the be surprisingly easy if you are willing to accept some caveats.

I’m going to assume that you have read the linked blog post, and here is the code that implements it:

public class PhantomReferenceQueue<THandle>
{
    private BlockingCollection<THandle> _queue = new BlockingCollection<THandle>();
    private ConditionalWeakTable<object, PhatomReference> _refs = new ConditionalWeakTable<object, PhatomReference>();

    public void Register(object instance, THandle handle)
    {
        _refs.Add(instance, new PhantomReference(this, handle));
    }

    public void Unregister(object instance)
    {
        if (_refs.TryGetValue(instance, out var val))
            GC.SuppressFinalize(val);
        _refs.Remove(instance);
    }

    public THandle Poll()
    {
        return _queue.Take();
    }

    private class PhantomReference
    {
        THandle _handle;
        PhantomReferenceQueue<THandle> _parent;

        public PhantomReference(PhantomReferenceQueue<THandle> parent, THandle handle)
        {
            _parent = parent;
            _handle = handle;
        }

        ~PhantomReference()
        {
            _parent._queue.Add(_handle);
        }
    }
}

Here is what it gives you:

  • You can have any number of queues.
  • You can associate an instance with a queue at any time, including far after it was constructed.
  • Can unregister from the queue at any time.
  • Can wait (or easily change it to do async awaits, of course) for updates about phantom references.
  • Can process such events in parallel.

What about the caveats?

This utilizes the finalizer internally, to inform us when the associated value has been forgotten, so it takes longer than one would wish for. This implementation relies on ConditionalWeakTable to do its work, by creating a weak associating between the instances you pass and the PhantomReference class holding the handle that we’ll send to you once that value has been forgotten.