你需要处理对象并将它们设置为null吗?
你是否需要处理对象并将它们设置为null,或者当垃圾收集器超出范围时将它们清理干净?
对象将在不再使用时以及垃圾收集器看起来合适时清理。 有时,您可能需要将对象设置为null
才能使其超出范围(例如,您不再需要的值的静态字段),但总体上通常不需要将其设置为null
。
关于处理对象,我同意@Andre。 如果对象是IDisposable
, 那么当您不再需要时处理它是个好主意 ,特别是如果对象使用非托管资源。 不处置非托管资源将导致内存泄漏 。
一旦程序离开using
语句的范围,您可以使用using
语句自动处理对象。
using (MyIDisposableObject obj = new MyIDisposableObject())
{
// use the object here
} // the object is disposed here
这在功能上等同于:
MyIDisposableObject obj;
try
{
obj = new MyIDisposableObject();
}
finally
{
if (obj != null)
{
((IDisposable)obj).Dispose();
}
}
在C#中,对象不会像C ++中那样超出范围。 垃圾收集器在不再使用时会自动处理它们。 这是一个比C ++更复杂的方法,其中变量的范围完全是确定性的。 CLR垃圾回收器主动遍历所有已创建的对象,如果它们正在使用,则会执行。
一个对象可以在一个函数中“超出范围”,但是如果它的值被返回,那么GC会查看调用函数是否保留在返回值上。
将对象引用设置为null
是不必要的,因为垃圾回收可以通过计算出哪些对象正在被其他对象引用。
在实践中,你不必担心破坏,它只是工作,它是伟大的:)
Dispose
完毕后,必须在实现IDisposable
所有对象上调用Dispose
。 通常情况下,你会使用这样的对象的using
块:
using (var ms = new MemoryStream()) {
//...
}
编辑变量范围。 Craig询问变量作用域是否对对象生存期有任何影响。 为了正确解释CLR的这个方面,我需要解释一些来自C ++和C#的概念。
实际变量范围
在这两种语言中,变量只能在与定义相同的范围内使用 - 类,函数或用大括号括起来的语句块。 然而,细微的差别是,在C#中,变量不能在嵌套块中重新定义。
在C ++中,这是完全合法的:
int iVal = 8;
//iVal == 8
if (iVal == 8){
int iVal = 5;
//iVal == 5
}
//iVal == 8
在C#中,但是你会得到一个编译器错误:
int iVal = 8;
if(iVal == 8) {
int iVal = 5; //error CS0136: A local variable named 'iVal' cannot be declared in this scope because it would give a different meaning to 'iVal', which is already used in a 'parent or current' scope to denote something else
}
如果您查看生成的MSIL,这是有道理的 - 函数使用的所有变量都是在函数的开头定义的。 看看这个功能:
public static void Scope() {
int iVal = 8;
if(iVal == 8) {
int iVal2 = 5;
}
}
以下是生成的IL。 请注意,在if块中定义的iVal2实际上是在函数级定义的。 实际上,这意味着就变量生命周期而言,C#仅具有类和函数级范围。
.method public hidebysig static void Scope() cil managed
{
// Code size 19 (0x13)
.maxstack 2
.locals init ([0] int32 iVal,
[1] int32 iVal2,
[2] bool CS$4$0000)
//Function IL - omitted
} // end of method Test2::Scope
C ++作用域和对象生存期
只要在堆栈上分配的C ++变量超出范围,就会被破坏。 请记住,在C ++中,您可以在堆栈或堆上创建对象。 当你在堆栈上创建它们时,一旦执行离开作用域,它们会从堆栈中弹出并被破坏。
if (true) {
MyClass stackObj; //created on the stack
MyClass heapObj = new MyClass(); //created on the heap
obj.doSomething();
} //<-- stackObj is destroyed
//heapObj still lives
当在堆上创建C ++对象时,它们必须被明确销毁,否则就是内存泄漏。 尽管没有堆栈变量的问题。
C#对象生命周期
在CLR中,对象(即引用类型) 始终在托管堆上创建。 对象创建语法进一步加强了这一点。 考虑这个代码片段。
MyClass stackObj;
在C ++中,这将在堆栈上的MyClass
上创建一个实例并调用其默认构造函数。 在C#中,它会创建一个对MyClass
类的引用,它不指向任何东西。 创建类的实例的唯一方法是使用new
运算符:
MyClass stackObj = new MyClass();
从某种意义上说,C#对象很像使用C ++中的new
语法创建的对象 - 它们是在堆上创建的,但与C ++对象不同,它们由运行时管理,因此您不必担心会破坏它们。
由于对象始终在堆上,因此对象引用(即指针)超出范围的事实变得没有实际意义。 确定是否要收集对象的因素比简单存在对象的引用要多。
C#对象引用
Jon Skeet将Java中的对象引用与连接到气球的字符串(即对象)进行了比较。 同样的类比适用于C#对象引用。 他们只是指向包含对象的堆的位置。 因此,将其设置为null对对象生存期没有直接影响,气球继续存在,直到GC“弹出”它。
继续沿着气球类比,似乎合乎逻辑的是,一旦气球没有连接到它的琴弦,它就可以被销毁。 事实上,这正是引用计数对象在非托管语言中的工作方式。 除了这种方法不适用于循环引用。 设想两个气球通过一个字符串连接在一起,但气球中没有任何字符串。 在简单的参考计数规则下,即使整个气球组都是“孤儿”,它们都会继续存在。
.NET对象与屋顶下的氦气球非常相似。 当屋顶打开时(GC运行) - 未使用的气球飘走,即使可能有一组气球被系在一起。
.NET GC使用分代GC和标记和扫描的组合。 分代方法涉及运行时偏爱检查最近分配的对象,因为它们更可能未被使用,并且标记和扫描涉及运行时贯穿整个对象图并计算出是否存在未使用的对象组。 这充分处理了循环依赖问题。
另外,.NET GC在另一个线程(所谓的终结器线程)上运行,因为它有相当多的工作要做,在主线程上这样做会中断你的程序。
正如其他人所说,如果类实现了IDisposable
你一定要调用Dispose
。 我对此持相当严格的立场。 例如,有人可能会声称,在DataSet
上调用Dispose
是毫无意义的,因为它们对它进行了反汇编,并且看到它没有做任何有意义的事情。 但是,我认为这个论点中有很多谬论。
阅读这个由受尊重的人士就这个问题进行有趣的辩论。 然后在这里阅读我的推理,为什么我认为杰弗里里克特在错误的营地。
现在,请确定您是否应该将引用设置为null
。 答案是不。 让我用下面的代码来说明我的观点。
public static void Main()
{
Object a = new Object();
Console.WriteLine("object created");
DoSomething(a);
Console.WriteLine("object used");
a = null;
Console.WriteLine("reference set to null");
}
那么你什么时候觉得a
引用的对象有资格收藏? 如果你在打电话给a = null
之后说,那么你错了。 如果你在Main
方法完成后说,那么你也是错的。 正确的答案是它在 DoSomething
调用期间的某个时间有资格收集。 那是对的。 在引用被设置为null
之前并且甚至在对DoSomething
的调用完成之前 ,它是符合条件的。 这是因为JIT编译器可以识别什么时候对象引用不再被解除引用,即使它们仍然是根。
上一篇: Do you need to dispose of objects and set them to null?