I thought to write a little demo program that demonstrates the difference. Turned out to be a bit more challenging than I counted on. First necessary ingredient is to ensure that the finalizer thread can be slowed down so you can observe the value of WeakReference.IsAlive without risking it being affected by a race with the finalizer thread. So I used:
class FinalizerDelayer {
~FinalizerDelayer() {
Console.WriteLine("Delaying finalizer...");
System.Threading.Thread.Sleep(500);
Console.WriteLine("Delay done");
}
}
Then a little class that will be target of the WeakReference:
class Example {
private int instance;
public Example(int instance) { this.instance = instance; }
~Example() {
Console.WriteLine("Example {0} finalized", instance);
}
}
Then a program that demonstrates the difference between a long and a short weak reference:
class Program {
static void Main(string[] args) {
var target1 = new Example(1);
var target2 = new Example(2);
var shortweak = new WeakReference(target1);
var longweak = new WeakReference(target2, true);
var delay = new FinalizerDelayer();
GC.Collect(); // Kills short reference
Console.WriteLine("Short alive = {0}", shortweak.IsAlive);
Console.WriteLine("Long alive = {0}", longweak.IsAlive);
GC.WaitForPendingFinalizers();
Console.WriteLine("Finalization done");
GC.Collect(); // Kills long reference
Console.WriteLine("Long alive = {0}", longweak.IsAlive);
Console.ReadLine();
}
}
You must run this program so the debugger cannot affect the life-time of objects. Select the Release build and change a debugger setting: Tools + Options, Debugging, General, untick the "Suppress JIT optimization" option.
Turned out the finalization order of objects truly is non-deterministic. The order is different every time you run the program. We want the FinalizerDelayer object to get finalized first but that doesn't always happen. I think it is a side-effect to the built-in Address Space Layout Randomization feature, it makes managed code very hard to attack. But run it often enough and you'll eventually get:
Delaying finalizer...
Short alive = False
Long alive = True
Delay done
Example 1 finalized
Example 2 finalized
Finalization done
Long alive = False
Long story short:
- A short weak reference sets IsAlive to false as soon as the object is collected and placed on the freachable queue, ready for it to be finalized. The object still physically exists but no strong references exist anymore, it will soon get finalized.
- A long weak reference keeps track of the object all the way through its true life time, including its life on the freachable queue. IsAlive won't be set to false until its finalizer completed.
Beware of a quirk when the object gets resurrected, moved back from the freachable queue to the normal heap when a strong reference is re-created. Not something I explored in this demo program but a long weak reference will be needed to observe that. The basic reason why you'd need a long weak reference.