事件处理程序很容易导致内存泄漏,因为事件的调用列表包含对事件处理实例的引用,因此,如果事件源仍处于活动状态,则无法垃圾收集事件处理实例。
但是请考虑以下代码:
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)
{
//...
}
}
我对不同事件处理版本可能导致内存泄漏的假设/问题:
MyClass
的实例。localHandler
的引用暗示了对MyClass
实例的引用-除非,如果localHandler
中的代码没有对{{ 1}}?MyClass
实例的引用-是吗? MyClass
的实例,这可能不会导致泄漏吗?然后,跟踪版本3和4的问题:
MyClass
实例保持活动状态的引用吗?MyClass
实例在MyClass
实例之后),因为无法使用{{1}删除它们}?编辑:这篇文章(原标题为“何时事件处理程序会导致内存泄漏?”)被建议与Why and How to avoid Event Handler memory leaks?重复,但我不同意,因为这个问题专门针对lambda事件处理程序。我改掉了这个问题/标题,以使其更加清楚。
答案 0 :(得分:1)
免责声明: 我不能保证这是100%的事实-您的问题很深,我可能会犯一个错误。
但是,我希望它能给您一些想法或方向。
让我们根据CLR
记忆组织来考虑这个问题:
局部方法变量和方法参数存储在内存中的方法堆栈框架中(除非它们用ref
关键字声明)。
堆栈存储指向堆中对象的值类型和引用类型变量引用。
方法执行时存在方法堆栈框架,方法结束后局部方法变量将随堆栈框架消失。
除了以一种或另一种方式捕获局部变量外,它还与编译器工作有关,您可以在Jon Skeet的网站上了解它:
http://jonskeet.uk/csharp/csharp2/delegates.html#captured.variables
版本1 :OnSomeEvent
方法是MyClass
的成员,它将被Someclass source
实例捕获,直到引用该方法的委托不会被删除。从事件。因此,MyClass
不会收集在构造函数中创建并放置在堆中并保存此方法的GC
实例,直到不会从事件中删除其方法引用为止。
编译器通过特定方式编译lambda,请完整阅读实施示例段落:
https://github.com/dotnet/csharplang/blob/master/spec/conversions.md#anonymous-function-conversions
版本4 :
我提供的提供踢Lambda的2个链接将被编译为MyClass
方法,该方法将由SomeClass
实例捕获,如 Version 1
版本2 : 我不知道如何编译本地方法的细微差别,但是应该与 Version 4 (因此, Version 1 )相同。
版本3 : 所有局部变量都将以有趣的方式捕获。
您也有'object x',因此将创建编译器生成的类,该类
将包含公共字段public object x;
和将从lambda转换而来的方法(请参见实施示例段落)。
因此,我认为在 1、2、4版在内部是相同的: MyClass将包含将用作事件处理程序的方法。
在版本3 中,将创建编译器生成的类,该类将保存从lamdba转换的本地变量和方法。
直到SomeClass
事件在调用列表中有其方法,GC才会收集任何类的任何实例。
source.SomeEvent = null
分配空值来删除订阅。
https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/events/how-to-subscribe-to-and-unsubscribe-from-events#unsubscribing