假设您有一个包含事件属性的类。如果在本地上下文中实例化此类,而没有外部引用,则会为事件分配lambda表达式,以防止实例被垃圾回收?
{
var o = new MyClass();
o.MyClassEvent += (args) => {};
}
// Will 'o' be eligible for garbage collection here?
答案 0 :(得分:5)
不,o
将被释放,lambda函数也将被释放。其他任何地方都没有引用o
,所以没有理由不让它被释放。
答案 1 :(得分:2)
简短回答,不,o
将被释放。别担心。
答案稍长:
您的代码或多或少地执行以下操作:
o
)。o
o
现在具有对该委托的引用。)在此过程中的任何时候,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可以重复使用存储,因为重新排序i
和o
并没有任何区别。
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#代码中使用它。