I have a class that holds on to a delegate, in order to lazily evaluate something later.
Once I've evaluated it, by calling the delegate, I clear out the reference to the delegate, hoping that it would be eligible for collection. After all, it might hold on to a world of local variables if it was constructed as an anonymous method.
I tried building a unit-test to verify this, but it doesn't seem to work out the way I planned, instead it seems that either my assumptions about WeakReference
(which I used for test purposes here), or some other assumption, doesn't hold water.
Take a look at this code, which you can run in LINQPad
void Main()
{
WeakReference wr;
Lazy<int> l;
CreateTestData(out wr, out l);
wr.IsAlive.Dump(); // should be alive here
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
wr.IsAlive.Dump(); // and alive here as well
l.Value.Dump(); // but now we clear the reference
GC.Collect(); // so one of these should collect it
GC.WaitForPendingFinalizers();
GC.Collect();
wr.IsAlive.Dump(); // and then it should be gone here
GC.KeepAlive(l);
}
void CreateTestData(out WeakReference wr, out Lazy<int> l)
{
Func<int> f = () => 10;
wr = new WeakReference(f);
l = new Lazy<int>(f);
}
public class Lazy<T>
{
private Func<T> _GetValue;
private T _Value;
public Lazy(Func<T> getValue)
{
_GetValue = getValue;
}
public T Value
{
get
{
if (_GetValue != null)
{
_Value = _GetValue();
_GetValue = null;
}
return _Value;
}
}
}
I was assuming that:
WeakReference
and the Lazy<T>
objectsLazy<T>
object to relinquish its reference to the delegate, which would reduce the references to only the one that WeakReference
is holding on toWeakReference
WeakReference
would indicate that the object is no longer aliveThe output of the code was thus expected to be (with comments):
true // not gc'ed after construction
true // not gc'ed after full GC, still beind held by Lazy<T>
10 // value from calling delegate
false // but is now gc'ed, Lazy<T> no longer has a reference to it
But instead the output is:
true
true
10
true
Can anyone shed some light on what I'm missing here?
The "problem" is that the compiler is noticing that it can reuse a single delegate instance forever. It doesn't capture any context, not even the implicit this
reference. So this:
void CreateTestData(out WeakReference wr, out Lazy<int> l)
{
Func<int> f = () => 10;
...
}
Is turned into something like:
static Func<int> hiddenDelegate;
static int HiddenMethod()
{
return 10;
}
void CreateTestData(out WeakReference wr, out Lazy<int> l)
{
if (hiddenDelegate == null)
{
hiddenDelegate = HiddenMethod;
}
Func<int> f = hiddenDelegate;
...
}
Look at the code in ildasm (or Reflector with no optimization turned on) to see exactly what's going on.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With