Comparable WeakReferences in C#

C# provides us with a WeakReference object. This object behaves like a normal reference, except that it does not prevent garbage collection of the target instance it refers to. So if every other strong reference to an instance goes away or is nulled out, the .NET garbage collector will collect that instance, without regard to the weak references. WeakReferences also provide an IsAlive property so you can check if the target still exists.

The WeakReference class has a weakness in certain situations that I’d like to address. Let me start with an example.

Suppose you have two weak references and you want to know if they point to the same instance. Something like this (very naive) example:

This will actually fail every time. It’s comparing the references to the WeakReferences. Because ref1 is a reference to a WeakReference instance, as is ref2. What we really want is to compare the targets. (Usually.)

So here I present the EquatableWeakReference class. It behaves just like a WeakReference, but if you compare two of them, it compares the targets.

As you can see, it has a similar constructor, and the same properties as the WeakReference. It wraps the WeakReference, rather than inheriting from it, because we originally wrote this for use in Silverlight and you can’t inherit from WeakReference in Silverlight.

When you set the target, it records the target’s HashCode. This is a unique* identifier of that object instance. It uses those HashCodes to do comparisons. It also overrides GetHashCode to return the target’s HashCode, rather than the EquatableWeakReference’s HashCode. This also provides the advantage that if you use an EquatableWeakReference as a key in a Dictionary, it will be treated as the same key as a real reference to that object, because Dictionaries use the HashCode to index values.

(Which brings us to the whole reason for creating this. We needed to make a WeakDictionary. A Dictionary that would allow values to be garbage collected if the key instance was no longer alive. This was a single-point fix to a widespread memory leak in an open-source library we were working with. Oh, memory leaks… How you amuse me…)

* – It’s not really 100% unique, but it was good enough for our purposes.

1) You did mention that hash codes are not always unique. So you’re Equals functions should really first compare the hash codes, and then if traget is alive, compare target to obj argument. If target isn’t alive, I’d think you’d want Equals to return false, but I guess it depends on the use of this dictionary.

2) The other concern is more serious. You still have leak, since entries in the dictionary never get cleaned-up. At least the weakreference will allow the GC to get clean-up the objects you’re pointing to, but there is no good way to get rid of the dictionary entries. They will continue to grow. See discussion at…

2) I didn’t present the code for my WeakDictionary, but it did have cleanup built in. There are a number of different strategies that could be used, with various strengths and weaknesses to choose from, depending on your use case. In our case, the WeakDictionaries had heavy loads on them. We ended up running a cleanup once every 50,000 times it was accessed. That number was tuned for our application to balance performance with variability of memory usage over time.