Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delegates do not get garbage collected [duplicate]

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?

like image 873
Pavel Tupitsyn Avatar asked Mar 04 '14 13:03

Pavel Tupitsyn


1 Answers

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.

like image 186
Jon Skeet Avatar answered Sep 21 '22 03:09

Jon Skeet