C# Tips & Tricks: Weak References - When and How to Use Them
Sometimes you have an object which is very large and needed multiple times, but not constantly, throughout your application. For example a huge lookup table, or the contents of a large file you need in memory for some algorithm. To work with this object, you traditionally have two options:
- As a local variable inside the method that needs it
- As a field in a class that lives during the whole time the object could be needed
Both solutions aren't optimal if your object is very huge, and only sometimes needed:
The class field solution keeps the object in memory the whole time, giving your application a huge permanent memory footprint increase. And the local variable solution will decrease application performance, since the object not only has to be created anew every time it's needed, but also deleted each time it goes out of scope, generating work for the Garbage Collector.
If the creation of the object is very expensive, and you want to avoid doing it multiple times, the class field solution is your way to go. But in all cases where the object is a cheap-to-create memory hog, a better solution than both of the above would be welcome.
Luckily for us, .Net 4.0 gives us a middle-ground solution in the form of weak references.
Usually, when an object goes out of scope, or is set to null, it is no longer accessible to us, even if the garbage collector will only delete the object much later.
A weak reference object will keep a reference to an object that went out of scope or was set to null, until it is actually deleted by the garbage collector. But until that happens, we can get the object back!
So our new option to work with a large object is to use a weak reference to it. The weak reference itself should become a long-living class member. And in the method that needs our huge object, we check if the weak reference holds an object, which we will use if it does. And if not, we create a new instance.
This way, when we first run our method, a new instance of the huge object is created. But when we run it the next time, and the garbage collector hasn't deleted the object in the meantime, we can reuse it, and don't need to create a new instance.
The actual usage is pretty simple.
To create a weak reference to an object, we simply call the constructor with the object:
WeakReference w = new WeakReference(MyHugeObject);To get the underlying object from a weak reference, we use its .Target property:
MyHugeObject = w.Target as MyHugeClass;If the object still exists, it is returned. If it was claimed by the garbage collector, a null refence is returned by the 'as' operator.
So, given that we have a WeakReference w as a field in our class, we would use it inside the method that does something with a HugeObject like this:
static void Func()First we declare the MyHugeObject variable, which will be initialized to null by the compiler. Then we check if our weak refence is null (which is the case when Func runs for the very first time) or if the assignment from w.Target returned null. If either is the case, we (re)create MyHugeObject and assign it to w, after which we can then do some work with MyHugeObject.
Now, WeakReference does us one huge favor: it takes care of all members of MyHugeObject, and makes sure we only get it back if all its members are still intact, so we don't need to worry that we get back an incomplete object.
A drawback is that WeakReference doesn't play well with objects that implement IDisposable, since we cannot call Dispose() (or have it called automatically through the 'using' statement) through a WeakRefence to the object.
And finally a word of warning: weak references aren't a guaranteed profit for application performance. In most cases, they will make an algorithm more performant than when using very large local variables. But it's not guaranteed, and in some cases it could produce noticeable overhead (for example when the huge object is a data structure consisting of many smaller objects with references to each other, and a WeakRefence to the data structure turns all those internal refernces to weak references, this might incur garbage collector overhead, because every reference has to be checked to decide if the object as a whole can be recovered or deleted).
So the best advice with weak references is: profile or benchmark it, to make sure that you choose the best solution for your specific situation.
Andreas Hartl, 2010-2011