Below is a console app that demonstrates the issue:
class Program
{
static void Main()
{
InitRefs();
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine(_refObj.IsAlive);
Console.WriteLine(_refAction.IsAlive);
Console.WriteLine(_refEvent.IsAlive);
Console.ReadKey();
}
private static void InitRefs()
{
_refObj = new WeakReference(new object());
_refAction = new WeakReference((Action) (() => { }));
_refEvent = new WeakReference(new EventHandler((sender, eventArgs) => { }));
}
private static WeakReference _refObj;
private static WeakReference _refAction;
private static WeakReference _refEvent;
}
Output is "False True True".
I've used SOS.dll to try to find what holds the delegates from being GCed and here is what I get for the Action:
!gcroot 02472584
HandleTable:
006613ec (pinned handle)
-> 03473390 System.Object[]
-> 02472584 System.Action
Can someone explain what is going on?
Your delegates don't capture anything, so the compiler basically caches them. You can see this in action with this short program:
using System;
class Program
{
static void Main()
{
Action action1 = GetAction();
Action action2 = GetAction();
Console.WriteLine(ReferenceEquals(action1, action2)); // True
}
private static Action GetAction()
{
return () => {};
}
}
There are autogenerated static fields in the class which are lazily populated. Basically it's an optimization to avoid creating many delegate objects which all refer to the same static method with no context to differentiate them.
Yes, it means that the delegates themselves won't get garbage collected - but they're very lightweight (they're not stopping anything else from being garbage collected, as they're not capturing any variables).
As an example of a situation where the delegates can't be cached (and are therefore eligible for garbage collection), change the InitRefs
method to:
private static void InitRefs(int x)
{
_refObj = new WeakReference(new object());
_refAction = new WeakReference((Action) (() => x.ToString() ));
_refEvent = new WeakReference(new EventHandler((sender, eventArgs) =>
x.ToString()));
}
That then prints False three times, as the delegates capture the x
parameter, and so can't be cached.
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