2
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();
        }
    }
}

为什么 GC 后 w2.Target 不为空?

    w1.Target:[Test.Test+HandleMessage]
    w2.Target:[Test.Test+HandleMessage]
    GC后
    w1.目标:[]
    w2.Target:[Test.Test+HandleMessage]

编辑

感谢所有的答案,Brian Rasmussen 和 Jon Skeet 你的答案是正确的。现在我彻底明白了这是怎么回事,所以我又写了一个例子来让一切更清楚。

以下示例表明:

如果 Test#create() 没有引用任何实例属性或方法,那么编译器将创建“private static HandleMessage CS$<>9__CachedAnonymousMethodDelegate1”,就像 Jon Skeet 所说的那样 - 当你使用相同的lambda 表达式多次。

如果 Test#create() 确实引用了实例属性或方法,如下例调用 this.ToString(); 那么编译器无法创建静态方法来替换实例方法的逻辑,所以在GC之后可以收集HandleMessage实例。

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();
        }
    }
}
4

3 回答 3

6

它与 lambda 无关。对于匿名委托,可以观察到相同的行为。因此,如果您将代码更改为

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

你得到相同的结果。

在第一种情况下,您在 Test 的实例上有一个实例方法。由于您在为空时没有对此实例的其他引用listener1,因此它可能会被收集。

在第二种情况下,匿名方法必须放在某种类型上(因为方法不能单独存在)。在这种情况下,编译器将匿名方法作为静态方法放置在您的Test类中。此外,引用存储在Test类型的静态成员中。因此Type也有对该方法的静态引用,这就是它在集合中存在的原因。

查看 IL 以了解事物的连接方式。

于 2009-02-11T09:39:33.277 回答
2

lambda 表达式缓存在类的静态字段中 - 当我编译它时,它位于CS$<>9__CachedAnonymousMethodDelegate1. 当您多次使用相同的 lambda 表达式时,这会提高效率,但这意味着它不会被垃圾收集。

查看生成的 IL 以了解我的意思。

如果 lambda 表达式捕获任何变量,我不相信它会被缓存(因为它不可能!)。因此,如果您更改代码以使用:

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

然后你会看到w2.Target垃圾收集后变为空。

于 2009-02-11T09:26:14.993 回答
-1

force-collect-memory 的常见模式是:

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

此外,GC 可以免费不收集东西 :)

于 2009-02-11T09:21:55.550 回答