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";<span style="color: #0000ff">public</span> Func<<span style="color: #0000ff">bool</span>> GetFunc1() { <span style="color: #0000ff">return</span> () => 1 == 1; } <span style="color: #0000ff">public</span> Func<<span style="color: #0000ff">string</span>> GetFunc2() { <span style="color: #0000ff">return</span> () => 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(<span style="color: #006080">"Press Enter to continue."</span>); Console.ReadLine(); } <span style="color: #0000ff">private</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> TestFuncInstance(Delegate func) { Thread.Sleep(1000);<span style="color: #008000">// give some time for GC</span> Console.WriteLine(<span style="color: #0000ff">string</span>.Format(<span style="color: #006080">"Method: {0}"</span>,func.Method)); Console.WriteLine(<span style="color: #0000ff">string</span>.Format(<span style="color: #006080">"DeclaringType: {0}"</span>, func.Method.DeclaringType)); Console.WriteLine(<span style="color: #0000ff">string</span>.Format(<span style="color: #006080">"Target: {0}"</span>, func.Target ?? <span style="color: #006080">"null"</span>)); 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: nullMethod: 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.
