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

时间:2012-06-27 13:00:30

标签: c# events lambda garbage-collection

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

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

4 个答案:

答案 0 :(得分:5)

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

答案 1 :(得分:2)

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

答案稍长:

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

  1. 在该线程上创建一些本地存储,以引用新的MyClass(o)。
  2. 创建新的MyClass
  3. o
  4. 存储对新MyClass的引用
  5. 从lambda创建一个新委托。
  6. 将该委托分配给该事件(o现在具有对该委托的引用。)
  7. 在此过程中的任何时候,GC都可能会停止线程,然后检查哪些对象是否有根。根是静态的对象或在线程的本地存储中(我指的是给定线程执行中的局部变量,由堆栈实现,而不是“线程局部存储”,它本质上是一种静态形式)。根对象是根,由它们引用的对象,由它们引用的对象,等等。根本物品将不会被收集,其余的将被收集(除非我们现在将忽略一些与终结者有关的额外内容)。

    在创建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用于i的相同本地存储,因此即使它仍在范围内,也有资格收集(“在范围内”意味着程序员可以选择用它做某事,但他或她没有,编译的代码不需要这样的概念)。

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

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

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

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

答案 2 :(得分:1)

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

答案 3 :(得分:0)

这取决于“本地环境”的具体含义。比较:

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#代码中使用它。