Do Anonymous Methods Prevent Declaring Types from Being GC’d?


It seems that they do if they reference members of the declaring type. This makes perfect sense now that I think about it, but I didn’t think about it earlier and wrote some code that caused a memory leak. I had an object acting as a singleton and referencing a delegate instance that was created from an object acting as a non singleton. Bam! Memory Leak.

I setup a little test to demonstrate. Here is a class that has two methods which return delegate instances:

public class TestSource
{
    private string internalValue = "test";

    public Func<bool> GetFunc1()
    {
        return () => 1 == 1;
    }

    public Func<string> GetFunc2()
    {
        return () => internalValue;
    }
}

Notice that GetFunc1() doesn’t have any references to internal members, but GetFunc2() does. Here’s the test class:

class Program
{
    static void Main(string[] args)
    {
        // hold no references to TestSource
        TestFuncInstance(new TestSource().GetFunc1());
        TestFuncInstance(new TestSource().GetFunc2());

        Console.WriteLine("Press Enter to continue.");
        Console.ReadLine();
    }

    private static void TestFuncInstance(Delegate func)
    {
        Thread.Sleep(1000);// give some time for GC

        Console.WriteLine(string.Format("Method: {0}",func.Method));
        Console.WriteLine(string.Format("DeclaringType: {0}", func.Method.DeclaringType));
        Console.WriteLine(string.Format("Target: {0}", func.Target ?? "null"));

        Console.WriteLine();
    }
}

This creates two separate instances of TestSource and passes the result of the two GetFunc methods to a test method. Notice that there are no declared variable references to the TestSource object. Here’s the output of the test:

Method: Boolean <GetFunc1>b__0()
DeclaringType: TestingDelegates.TestSource
Target: null 

Method: System.String <GetFunc2>b__2()
DeclaringType: TestingDelegates.TestSource
Target: TestingDelegates.TestSource 

Press Enter to continue.

I know this test isn’t very scientific, but you’ll see that Func1’s target is null while Func2’s target is not. Func2 has to hold a reference to the declaring object so that it can do it’s job when invoked. Func1 does not need a reference, and seems to free up the declaring object to be garbage collected. This is definitely something to keep in mind when passing around delegates.

Technorati Tags: ,,
Synchronizing UI Operations with Asynchronous Dependencies