views:

335

answers:

4

namespace Test {

class Test
{
    delegate void HandleMessage(string message);

    public void handleMessage(string message){}

    static void Main(string[] args)
    {
        HandleMessage listener1 = new Test().handleMessage;
        WeakReference w1 = new WeakReference(listener1);

        HandleMessage listener2 = (message) => { };
        WeakReference w2 = new WeakReference(listener2);

        Console.WriteLine("w1.Target:\t[" + w1.Target + "]");
        Console.WriteLine("w2.Target:\t[" + w2.Target + "]");

        listener1 = null;
        listener2 = null;
        GC.Collect();
        Console.WriteLine("after GC");

        Console.WriteLine("w1.Target:\t[" + w1.Target + "]");
        Console.WriteLine("w2.Target:\t[" + w2.Target + "]");

        Console.ReadLine();
    }
}

}

why w2.Target is not null after GC.

w1.Target: [Test.Test+HandleMessage]
w2.Target: [Test.Test+HandleMessage]
after GC
w1.Target: []
w2.Target: [Test.Test+HandleMessage]

A: 

The common pattern for force-collect-memory is:

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

Moreover, GC is free not to collect stuff :)

Anton Gogolev
There are no finalizers, so the OP's code will suffice in this case.
Brian Rasmussen
+2  A: 

The lambda expression is cached in a static field in the class - when I compiled it, it was in CS$<>9__CachedAnonymousMethodDelegate1. That makes it more efficient when you use the same lambda expression multiple times, but it means it won't get garbage collected.

Look at the generated IL to see what I mean.

If the lambda expression captures any variables, I don't believe it will be cached (because it can't be!). So if you change your code to use:

string x = "hello";
HandleMessage listener2 = message => Console.WriteLine(x);

then you'll see w2.Target become null after garbage collection.

Jon Skeet
For some reason I did not get a "load answers" while typing. Odd since you posted your answer 13 minutes before I did.
Brian Rasmussen
+4  A: 

It has nothing to do with lambdas. The same behavior can be observed for anonymous delegates. So if you change to code to

HandleMessage listener2 = delegate(string message) => { };

you get the same result.

In the first case you have an instance method on an instance of Test. Since you have no other references to this instance when listener1 is nulled, it may be collected.

In the second case the anonymous method must be placed on some type (as methods cannot exist on their own). In this case the compiler places the anonymous method as a static method on your Test class. Furthermore the reference is stored in a static member on the Test type. Thus Type has a static reference to the method as well, which is why it survives a collection.

Take a look at the IL to see how things are wired.

Brian Rasmussen
A: 

thank for all the answers, Brian Rasmussen and Jon Skeet your answers are correct. Now i thoroughly understand what is going on, so i wrote another example to make everything more clearly.

The following example show that :

if Test#create() method don't reference to any Test instance object's properties or methods, then "private static HandleMessage CS$<>9__CachedAnonymousMethodDelegate1" will be created by compiler, like what Jon Skeet has said - That makes it more efficient when you use the same lambda expression multiple times.

if Test#create() method reference to any Test instance object's properties or methods, like the example below calling this.toString(); then compiler can not create static method to replace the intstance's method logic, so after GC the HandleMessage instance can be collected.

namespace Test {

class Test
{
    public delegate void HandleMessage(string message);

    public void handleMessage(string message)
    {
    }

    public HandleMessage create()
    {
        return (message) => { 
            //this.ToString(); 
        };
    }       

    static void Main(string[] args)
    {
        HandleMessage listener1 = new Test().handleMessage;
        WeakReference w1 = new WeakReference(listener1);

        HandleMessage listener2 = new Test().create();//(message) => { };
        WeakReference w2 = new WeakReference(listener2);

        Console.WriteLine("w1.Target:\t[" + w1.Target + "]");
        Console.WriteLine("w2.Target:\t[" + w2.Target + "]");

        listener1 = null;
        listener2 = null;
        GC.Collect();
        Console.WriteLine("after GC");

        Console.WriteLine("w1.Target:\t[" + w1.Target + "]");
        Console.WriteLine("w2.Target:\t[" + w2.Target + "]");

        Console.ReadLine();
    }
}

}