分配给事件的lambda会阻止拥有对象的垃圾收集吗?

假设您有一个带有事件属性的类。 如果在本地上下文中实例化此类,而没有外部引用,则会为事件分配lambda表达式,以防止实例被垃圾回收?

{ var o = new MyClass(); o.MyClassEvent += (args) => {}; } // Will 'o' be eligible for garbage collection here? 

不, o将被释放,lambda函数也将被释放。 从其他任何地方都没有引用o ,所以没有理由不应该释放它。

简短的回答,不, o将被释放。 别担心。

答案稍长一点:

您的代码或多或少地执行以下操作:

  1. 在该线程上创建一些本地存储,以引用新的MyClass( o )。
  2. 创建新的MyClass
  3. o处存储对新MyClass的引用。
  4. 从lambda创建一个新的委托。
  5. 将该委托分配给该事件( o现在具有对该委托的引用)。

在此期间的任何时候,GC都可以停止线程,然后检查哪些对象是否有根。 根是静态的对象或在线程的本地存储中(我指的是给定线程执行中的局部变量,由堆栈实现,而不是“线程局部存储”,它本质上是一种静态forms)。 根对象是根,由它们引用的对象,由它们引用的对象,等等。 根本物体将不会被收集,其余的将被收集(除非我们将暂时忽略一些额外的东西与终结者)。

在创建MyClass对象之后到目前为止,它还没有被线程的本地存储器所植根。

在创建委托对象之后的任何时候,它都没有被线程的本地存储器或具有引用它的MyClass所植根。

现在,接下来会发生什么?

这取决于。 lambda不会让MyClass保持活着状态(MyClass保持活着状态,但是当MyClass移动时,lambda也是如此)。 这还不足以回答“Will’o’是否有资格在这里收集垃圾?” 虽然。

 void Meth0() { var o = new MyClass(); o.MyClassEvent += (args) => {}; }//o is likely eligible for collection, though it doesn't have to be. void Meth1() { int i = 0; { var o = new MyClass(); o.MyClassEvent += (args) => {}; }//o is likely not eligible for collection, though it could be. while(i > 100000000);//just wasting time } void Meth2() { { var o = new MyClass(); o.MyClassEvent += (args) => {}; int i = 0;//o is likely eligible for collection, though it doesn't have to be. while(i > 100000000);//just wasting time } } void Meth3() { { var o = new MyClass(); o.MyClassEvent += (args) => {}; var x0 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be. var x1 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be. var x2 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be. var x3 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be. var x4 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be. var x5 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be. var x6 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be. var x7 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be. var x8 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be. var x9 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be. var x10 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be. var x11 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be. int i = 0; while(i > 100000000);//just wasting time } } 

Meth0似乎是最简单的,但实际上并非如此,所以我们将回到它。

在Meth1中,实现很可能是保留本地存储,因为它不再需要,因此当o不在范围内时,实现仍然会使用本地存储。

在Meth2中,实现可以使用它为o for i使用的相同本地存储,所以即使它仍然在范围内,它也有资格收集(“范围内”意味着程序员可以选择用它做一些事情,但他或者她没有,编译的代码不需要这样的概念)。

Meth3更有可能重新使用存储,因为它的额外临时使用使得实现与开始时放弃所有存储相比,更有意义。

这些都不一定是这样的。 Meth2和Meth3可以在开始时预留该方法所需的所有存储空间。 Meth1可以重复使用存储,因为重新排序io没有区别。

Meth0更复杂,因为它可能取决于调用方法接下来对本地存储的作用,而不是当时的清理(两者都是合法的实现)。 IIRC总是有一个清理目前的实施,但我不确定,无论如何也没关系。

总之,范围不是相关的,但是编译器和后来的JITter是否可以并且确实利用与对象相关的本地存储。 甚至可以在调用其方法和属性之前清理对象(如果这些方法和属性不使用this对象或任何对象的字段,因为如果对象已被删除,它将正常工作! )。

当运行时离开该代码块时,不再有对该对象的引用,因此它会被垃圾收集。

这取决于你对“本地环境”的确切含义。 相比:

 static void highMem() { var r = new Random(); var o = new MyClass(); o.MyClassEvent += a => { }; } static void Main(string[] args) { highMem(); GC.Collect(); //yes, it was collected } 

至:

 static void Main(string[] args) { var r = new Random(); { var o = new MyClass(); o.MyClassEvent += a => { }; } GC.Collect(); //no, it wasn't collected } 

在我的环境中(Debug build,VS 2010,Win7 x64),第一个符合GC条件,第二个不符合条件(通过让MyClass占用200MB内存并在任务管理器中检查),即使它超出了范围。 我想这是因为编译器在方法的开头声明了所有局部变量,所以对于CLR, o不在范围之外,即使你不能在C#代码中使用它。