Good implementation of weak dictionary in .Net

ConditionalWeakTable Class uses weak keys and automatically removes the key/value entry as soon as no other references to a key exist outside the table.


One problem with simply holding a dictionary of WeakReference objects is that there's no way, short of enumerating the entire dictionary, of removing from the Dictionary any WeakReference objects whose targets go out of scope.

It would be helpful if a WeakReference could include a delegate which would be invoked when the primary target went out of scope. As far as I know, there's no way to do that. If you don't mind adding another field and a little code to the objects you're storing within your "weak dictionary", I'd suggest creating what I call a "Finasposer" object, whose sole field is a MethodInvoker; when disposed, the MethodInvoker should be nulled out; the finalizer should Interlocked.Exchange() the MethodInvoker to null and--if its old value was non-null--invoke it. The object to be written in the dictionary should create a new Finasposer object, with a delegate that will cause the key to be removed from the dictionary when convenient.

Note that the neither the finalizer nor any delegate invoked thereby should never directly manipulate the dictionary, nor do anything that would require acquiring a lock. If the Finasposer holds a delegate, that delegate itself is guaranteed to be valid when Finalize executes, but the object attached to the delegate, and any objects referenced thereby, may be in unexpected states. It should be safe, however, for the Finasposer-called method to add to a linked list a reference to the object that went out of scope. The Dictionary's Add, Remove, and other methods could poll the linked list to see if any of the WeakReferences therein had died and needed to be cleaned out.


You'll need to write it yourself. It should be relatively straight forward, implementing the IDictionary<T,T> interface and then storing the actual values as WeakReferences<T>. You can then check the values on add/select using TryGetTarget to see if they're still alive.

public class WeakDictionary <TKey,TValue> : IDictionary<TKey,TValue>
    where TValue : class
{
    private readonly Dictionary<TKey,WeakReference<TValue>> innerDictionary = new Dictionary<TKey,WeakReference<TValue>>();
    
    
    public TValue Index[ TKey key ]
    {
        get
        {
            // Use .TryGetTarget instead of .IsAlive and .Target
            if (this.innerDictionary.TryGetValue(key, out WeakReference<TValue> wf) && wf.TryGetTarget(out TValue value))
            {
                return value;
            }

            return null;
        }
        
    }
    
    private void Cull()
    {
        var deadKeys = this.innerDictionary.Where(kvp => kvp.Value.IsAlive).Select(kvp => kvp.Key).ToList();

        foreach (var key in deadKeys)
        {
            _ = this.innerDictionary.TryRemove(key);
        }
    }
}