假设您有一个具有事件属性的类。如果您在本地上下文中实例化此类,而没有外部引用,那么将 lambda 表达式分配给事件会阻止实例被垃圾收集吗?
{
var o = new MyClass();
o.MyClassEvent += (args) => {};
}
// Will 'o' be eligible for garbage collection here?
假设您有一个具有事件属性的类。如果您在本地上下文中实例化此类,而没有外部引用,那么将 lambda 表达式分配给事件会阻止实例被垃圾收集吗?
{
var o = new MyClass();
o.MyClassEvent += (args) => {};
}
// Will 'o' be eligible for garbage collection here?
不,o
将被释放,lambda 函数也将被释放。没有o
来自其他任何地方的引用,因此没有理由不应该释放它。
简短的回答,不,o
将被释放。别担心。
稍微长一点的答案:
您的代码或多或少地执行以下操作:
o
)。o
.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 可以重用存储,因为重新排序i
和o
分配没有区别。
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
}
在我的环境(调试版本,VS 2010,Win7 x64)中,第一个符合 GC 条件,而第二个没有(通过让 MyClass 占用 200MB 内存并在任务管理器中检查),即使它超出范围。我想这是因为编译器在方法的开头声明了所有局部变量,因此对于 CLR,o
即使您无法在 C# 代码中使用它,也不会超出范围。