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] 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();然后编译器无法创建静态方法来替换intstance方法的逻辑,因此在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();
}
}
}
答案 0 :(得分:6)
它与lambdas无关。匿名委托可以观察到相同的行为。因此,如果您将代码更改为
HandleMessage listener2 = delegate(string message) => { };
你得到相同的结果。
在第一种情况下,您在Test实例上有一个实例方法。由于在listener1
为空时您没有对此实例的其他引用,因此可能会收集它。
在第二种情况下,匿名方法必须放在某种类型上(因为方法本身不能存在)。在这种情况下,编译器将匿名方法作为静态方法放在Test
类上。此外,引用存储在Test
类型的静态成员中。因此Type
也具有对该方法的静态引用,这就是它在集合中存活的原因。
看一下IL,了解事情是如何连线的。
答案 1 :(得分:2)
lambda表达式缓存在类的静态字段中 - 当我编译它时,它位于CS$<>9__CachedAnonymousMethodDelegate1
中。这使得当您多次使用相同的lambda表达式时效率更高,但这意味着它不会被垃圾收集。
查看生成的IL以了解我的意思。
如果lambda表达式捕获任何变量,我不相信它会被缓存(因为它不能!)。因此,如果您更改要使用的代码:
string x = "hello";
HandleMessage listener2 = message => Console.WriteLine(x);
然后你会看到w2.Target
在垃圾收集后变为空。
答案 2 :(得分:-1)
强制收集内存的常见模式是:
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
此外,GC可以免费收集东西:)