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 where ref.Target == null