Lambda表达式导致弱引用的目标不能被GC?
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
它与 lambda 无关。 对于匿名委托,可以观察到相同的行为。 因此,如果您更改代码,
您会得到相同的结果。
在第一种情况下,您在 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
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 theTest
type. ThusType
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.
lambda 表达式缓存在类中的静态字段中 - 当我编译它时,它位于
CS$<>9__CachedAnonymousMethodDelegate1
中。 当您多次使用相同的 lambda 表达式时,这会提高效率,但这意味着它不会被垃圾收集。查看生成的 IL 就明白我的意思了。
如果 lambda 表达式捕获任何变量,我不相信它会被缓存(因为它不可能!)。 因此,如果您将代码更改为使用:,
那么您将看到
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:
then you'll see
w2.Target
become null after garbage collection.强制收集内存的常见模式是:
此外,GC 可以自由地不收集东西:)
The common pattern for force-collect-memory is:
Moreover, GC is free not to collect stuff :)