C# lambda事件处理程序会导致什么类型的内存泄漏?
事件处理程序很容易导致内存泄漏,因为事件的调用列表包含对事件处理实例的引用,因此如果事件源仍处于活动状态,则无法对事件处理实例进行垃圾收集C# lambda事件处理程序会导致什么类型的内存泄漏?,c#,events,memory-leaks,C#,Events,Memory Leaks,事件处理程序很容易导致内存泄漏,因为事件的调用列表包含对事件处理实例的引用,因此如果事件源仍处于活动状态,则无法对事件处理实例进行垃圾收集 但请考虑下面的代码: public class SomeClass { public event EventHandler SomeEvent; } public class MyClass { public MyClass(SomeClass source) { //VERSION 1 source.
但请考虑下面的代码:
public class SomeClass
{
public event EventHandler SomeEvent;
}
public class MyClass
{
public MyClass(SomeClass source)
{
//VERSION 1
source.SomeEvent += OnSomeEvent;
//VERSION 2
void localHandler(object s, EventArgs args) { Console.WriteLine("some action with(out) any references"); }
source.SomeEvent += localHandler;
//VERSION 3
var x = new object();
source.SomeEvent += (s, e) => { Console.WriteLine("some event fired, using closure x:" + x.GetHashCode()); };
//VERSION 4
source.SomeEvent += (s, e) => { Console.WriteLine("some action without any references"); };
}
private void OnSomeEvent(object sender, EventArgs e)
{
//...
}
}
我的假设/问题:为什么不同的事件处理版本可能导致内存泄漏:
- 版本1:因为调用目标明确引用了
的实例MyClass
- 版本2:因为对
的引用意味着对localHandler
实例的引用-除非MyClass
中的代码没有对localHandler
实例的引用MyClass
- 版本3:因为lambda包含一个闭包,闭包本身就是对
实例的引用,或者它是李>MyClass
- 版本4:因为lambda没有引用
的实例,这可能不会导致泄漏MyClass
- .Net为lambda/closure创建的“magic helper对象”存储在哪里?它是否(始终)包含一个将保持
实例活动的引用MyClass
- 如果lambda事件处理程序可能泄漏,则它们应仅在不存在问题的情况下使用(例如,
实例超过MyClass
实例),因为无法使用SomeClass
删除它们-=
编辑:这篇文章(最初的标题是“什么时候事件处理程序会导致内存泄漏?”)被认为是重复的,但我不同意,因为这个问题是专门针对lambda事件处理程序的。我重新表述了问题/标题,使之更清楚。免责声明: 我不能保证这是100%的事实-你的问题很深,我可能会犯错误 然而,我希望它能给你一些想法或方向
让我们考虑这个问题,根据代码> CLR < /COD>内存组织:
本地方法变量和方法参数存储在内存中的方法堆栈框架中(除非它们用ref
关键字声明)
堆栈存储指向堆中对象的值类型和引用类型变量引用
方法执行时存在方法堆栈帧,方法结束后局部方法变量将随堆栈帧一起出现
除了以某种方式捕获局部变量外,它还与编译器的工作有关,您可以在Jon Skeet的网站上阅读:
Version 1:OnSomeEvent
方法是MyClass
的成员,它将被Someclass source
实例捕获,直到引用此方法的委托不会从事件中移除。因此,GC
不会收集在构造函数中创建的、放置在堆中并保存此方法的MyClass
实例,直到它的方法引用不会从事件中删除
编译器以特定方式编译lambda,请完整阅读实现示例段落:
第4版:
我提供的2个链接将把lambda编译成MyClass
方法,它将被SomeClass
实例捕获,如版本1所示
第2版:
我不知道如何编译本地方法的细微差别,但它应该与第4版(因此,第1版)相同
第3版:
所有局部变量都将通过有趣的方式捕获
您还有“object x”,因此将创建编译器生成的类,这
将包含公共字段<代码>公共对象x代码>和将从lambda转换的方法(参见实现示例段落)
因此,我认为在版本1、2、4中,内部相同:
MyClass将包含将用作事件处理程序的方法
在版本3中,将创建编译器生成的类,它将保存从lamdba翻译过来的局部变量和方法
在SomeClass
事件的方法位于调用列表中之前,GC不会收集任何类的任何实例
- 我认为您指的“magic helper对象”是编译器生成的类实例
- 您可以通过将null分配给事件来删除订阅:
。source.SomeEvent=null