I have not personally come across a situation where I’ve needed to use WeakReference type in .Net, but the popular belief seems to be that it should be used in caches. Dr Jon Harrop gave a very good case against the use of WeakReferences in caches in his answer to this question.
I’ve also often heard AS3 developers talk about using weak references to save on memory footprint but based on the conversations I’ve had it seems to add complexity without necessarily accomplishing the intended goal, and the runtime behaviour is rather unpredictable. So much so that many simply give up on it and instead manage the memory usage more carefully/optimize their code to be less memory intensive (or making the trade-off of more CPU-cycles and smaller memory footprint).
Dr Jon Harrop also pointed out in his answer that the .Net weak references are not soft, and there’s an aggressive collection of weak references at gen0. According to MSDN, long weak references gives you the potential to recreate an object, but the state of the object remains unpredictable.
!
Given these characteristics, I can’t think of a situation where weak references would be useful, perhaps someone could enlighten me?
6
I’ve found legitimate practical applications of weak references in the following three real-world scenarios that actually happened to me personally:
Application 1: Event handlers
You’re an entrepreneur. Your company sells a spark lines control for WPF. Sales are great but support costs are killing you. Too many customers are complaining of CPU hogging and memory leaks when they scroll through screens full of spark lines. The problem is their app is creating new spark lines as they come into view but data binding is preventing the old ones from being garbage collected. What do you do?
Introduce a weak reference between the data binding and your control so that data binding alone will no longer prevent your control from being garbage collected. Then add a finalizer to your control that tears down the data binding when it gets collected.
Application 2: Mutable graphs
You’re the next John Carmack. You’ve invented an ingenius new graph-based representation of hierarchical subdivision surfaces that makes Tim Sweeney’s games look like a Nintendo Wii. Obviously I’m not going to tell you exactly how it works but it all centers on this mutable graph where the neighbors of a vertex can be found in a Dictionary<Vertex, SortedSet<Vertex>>
. The graph’s topology keep changing as the player runs around. There’s only one problem: your data structure is shedding unreachable subgraphs as it runs and you need to remove them or you’ll leak memory. Luckily you’re a genius so you know there is a class of algorithms specifically designed to locate and collect unreachable subgraphs: garbage collectors! You read Richard Jones’ excellent monograph on the subject but it leaves you perplexed and concerned about your imminent deadline. What do you do?
Simply by replacing your Dictionary
with a weak hash table you can piggyback the existing GC and have it automatically collect your unreachable subgraphs for you! Back to leafing through Ferrari adverts.
Application 3: Decorating trees
You’re hanging from the ceiling of a cyclindrical room at a keyboard. You’ve got 60 seconds to sift through some BIG DATA before someone finds you. You came prepared with a beautiful stream-based parser that relies upon the GC to collect fragments of AST after they’ve been analyzed. But you realise you need extra metadata on each AST Node
and you need it fast. What do you do?
You could use a Dictionary<Node, Metadata>
to associate metadata with each node but, unless you clear it out, the strong references from the dictionary to old AST nodes will keep them alive and leak memory. The solution is a weak hash table which keeps only weak references to keys and garbage collects key-value bindings when the key becomes unreachable. Then, as AST nodes become unreachable they are garbage collected and their key-value binding is removed from the dictionary leaving the corresponding metadata unreachable so it too gets collected. Then all you have to do after your main loop has terminated is slide up back through the air vent remembering to replace it just as the security guard comes in.
Note that in all three of these real-world applications that actually happened to me I wanted the GC to collect as aggressively as possible. That’s why these are legitimate applications. Everybody else is wrong.
5
Given these characteristics, I can’t think of a situation where weak references would be useful, perhaps someone could enlighten me?
Microsoft document Weak Event Patterns.
In applications, it is possible that handlers that are attached to event sources will not be destroyed in coordination with the listener object that attached the handler to the source. This situation can lead to memory leaks. Windows Presentation Foundation (WPF) introduces a design pattern that can be used to address this issue, by providing a dedicated manager class for particular events and implementing an interface on listeners for that event. This design pattern is known as the weak event pattern.
…
The weak event pattern is designed to solve this memory leak problem. The weak event pattern can be used whenever a listener needs to register for an event, but the listener does not explicitly know when to unregister. The weak event pattern can also be used whenever the object lifetime of the source exceeds the useful object lifetime of the listener. (In this case, useful is determined by you.) The weak event pattern allows the listener to register for and receive the event without affecting the object lifetime characteristics of the listener in any way. In effect, the implied reference from the source does not determine whether the listener is eligible for garbage collection. The reference is a weak reference, thus the naming of the weak event pattern and the related APIs. The listener can be garbage collected or otherwise destroyed, and the source can continue without retaining noncollectible handler references to a now destroyed object.
1
Let me put this out first and come back to it:
A WeakReference is useful when you want to keep tabs on an object, but you DO NOT want your observations to prevent that object from being collected
So let’s start from the beginning:
–apologies in advance for any unintentional offense, but I’m gonna back up to “Dick and Jane” level for a moment since one can never tell ones audience.
So when you’ve got an object X
– let’s specify it as an instance of class Foo
– it CANNOT live on it’s own (mostly true); In the same way that “No man is an island”, there are only a few ways that an object can promoted to Islandhood – although it’s called being a GC root in CLR speak. Being a GC Root, or having an established chain of connections/references to a GC root, is basically what determines whether or not Foo x = new Foo()
gets garbage collected.
If you cannot walk your way back to some GC root either by heap or stack walking, you are effectively orphaned, and will likely be marked/collected next cycle.
At this point, let’s look at some horrible-contrived examples:
First, our Foo
:
public class Foo
{
private static volatile int _ref = 0;
public event EventHandler FooEvent;
public Foo()
{
_ref++;
Console.WriteLine("I am #{0}", _ref);
}
~Foo()
{
Console.WriteLine("#{0} dying!", _ref--);
}
}
Fairly simple – it’s not thread safe, so don’t try that, but keeps a rough “reference count” of active instances and decrements when they are finalized.
Now let’s look at a FooConsumer
:
public class NastySingleton
{
// Static member status is one way to "get promoted" to a GC root...
private static NastySingleton _instance = new NastySingleton();
public static NastySingleton Instance { get { return _instance;} }
// testing out "Hard references"
private Dictionary<Foo, int> _counter = new Dictionary<Foo,int>();
// testing out "Weak references"
private Dictionary<WeakReference, int> _weakCounter = new Dictionary<WeakReference,int>();
// Creates a strong link to Foo instance
public void ListenToThisFoo(Foo foo)
{
_counter[foo] = 0;
foo.FooEvent += (o, e) => _counter[foo]++;
}
// Creates a weak link to Foo instance
public void ListenToThisFooWeakly(Foo foo)
{
WeakReference fooRef = new WeakReference(foo);
_weakCounter[fooRef] = 0;
foo.FooEvent += (o, e) => _weakCounter[fooRef]++;
}
private void HandleEvent(object sender, EventArgs args, Foo originalfoo)
{
Console.WriteLine("Derp");
}
}
So we’ve got an object that’s already a GC root of it’s own (well…to be specific, it’ll be rooted via a chain straight to the app domain running this application, but that’s another topic) that has two methods of latching on to a Foo
instance – let’s test it out:
// Our foo
var f = new Foo();
// Create a "hard reference"
NastySingleton.Instance.ListenToThisFoo(f);
// Ok, we're done with this foo
f = null;
// Force collection of all orphaned objects
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Now, from the above, would you expect the object-that-was-once-referred-to by f
to be “collectable”?
No, because there is another object now holding a reference to it – the Dictionary
in that Singleton
static instance.
Ok, let’s try the weak approach:
f = new Foo();
NastySingleton.Instance.ListenToThisFooWeakly(f);
// Ok, we're done with this foo
f = null;
// Force collection of all orphaned objects
// This should collect # 2 - you'll see a "#2 dying"
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Now, when we whack our reference to the-Foo
-that-was-once-f
, there are no more “hard” references to the object, so it is collectable – the WeakReference
created by the weak listener won’t prevent that.
Good use cases:
-
Event handlers (Although read this first: Weak Events in C#)
-
You’ve got a situation where you would cause a “recursive reference” (i.e., object A refers to object B, which refers to object A, also referred to as a “Memory Leak”)(edit: derp, of course this isn’t true) -
You want to “broadcast” something to a collection of objects, but you don’t want to be the thing keeping them alive; a
List<WeakReference>
can be maintained easily, and even pruned by removing whereref.Target == null
3
Like logical leaks which are really hard to track down while users just tend to notice that running your software for a long time tends to take more and more memory and get slower and slower until they restart? I don’t.
Consider what happens if, when the user requests to remove application resource above, Thing2
fails to properly handle such an event under:
- Pointers
- Strong References
- Weak References
… and under which one of these such a mistake would likely be caught during testing, and which one wouldn’t and would fly under the radar like a stealth fighter bug. Shared ownership is, more often than most, a nonsensical idea.
A very illustrative example of weak references used to good effect is the ConditionalWeakTable, which is used by the DLR (among other places) to attach additional “members” to objects.
You don’t want the table to keep the object alive. This concept simply could not work without weak references.
But it kind of seems to me that all the uses for weak references came long after they were added to the language, since weak references have been part of .NET since version 1.1. It just seems like something you’d want to add, so that the lack of deterministic destruction won’t back you into a corner as far as language features are concerned.
1
If you have cache layer implemented with C# it’s much better too put your data in cache as weak references, it could help to improve your cache layer performance.
Think that approach also could be applied to session implementation. Because session is long living object most of the time, it could be some case when you have no memory for new user. In that case it will be much better to delete some else user session object then throwing OutOfMemoryException.
Also, if you have a large object in your application (some big lookup table, etc), that should be used quite seldom and recreating of such an object isn’t a very expensive procedure. Then better have it like a week reference to have a way to free your memory when you really need that.
3