Lambda表达式导致弱引用的目标不能被GC?

发布于 2024-07-13 15:47:18 字数 2672 浏览 5 评论 0原文

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不为null?

    w1.Target:      [Test.Test+HandleMessage]
    w2.Target:      [Test.Test+HandleMessage]
    after GC
    w1.Target:      []
    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();
        }
    }
}
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 is 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]

EDIT

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() doesn't reference any instance 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() does reference instance properties or methods, like the example below calling this.ToString(); then the compiler can not create a static method to replace the intstance method's 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();
        }
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

维持三分热 2024-07-20 15:47:18

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

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

您会得到相同的结果。

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

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

查看 IL 以了解事物是如何连接的。

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.

同尘 2024-07-20 15:47:18

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

查看生成的 IL 就明白我的意思了。

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

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

那么您将看到 w2.Target 在垃圾回收后变为 null。

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.

默嘫て 2024-07-20 15:47:18

强制收集内存的常见模式是:

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

此外,GC 可以自由地不收集东西:)

The common pattern for force-collect-memory is:

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

Moreover, GC is free not to collect stuff :)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文