9

假设您有一个具有事件属性的类。如果您在本地上下文中实例化此类,而没有外部引用,那么将 lambda 表达式分配给事件会阻止实例被垃圾收集吗?

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

4 回答 4

6

不,o将被释放,lambda 函数也将被释放。没有o来自其他任何地方的引用,因此没有理由不应该释放它。

于 2012-06-27T13:03:34.910 回答
2

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

稍微长一点的答案:

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

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

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

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

在创建委托对象之后,到目前为止,它还没有被线程的本地存储或 MyClass 引用过。

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

这取决于。lambda 不会让 MyClass 保持活动状态(MyClass 保持活动状态,但是当 MyClass 消失时,lambda 也会保持活动状态)。这还不足以回答“'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对象的任何字段,因为如果对象已被删除,它会正常工作!) .

于 2012-06-27T14:15:44.790 回答
1

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

于 2012-06-27T13:04:00.797 回答
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
}

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

于 2012-06-27T13:30:19.150 回答