题
我对 CLR 和 GC 的工作方式着迷(我正在通过 C# 阅读 CLR、Jon Skeet 的书籍/帖子等来扩展我在这方面的知识)。
无论如何,说:
MyClass myclass = new MyClass();
myclass = null;
或者,通过让 MyClass 实现 IDisposable 和析构函数并调用 Dispose()?
另外,如果我有一个带有 using 语句的代码块(例如下面),如果我单步执行代码并退出 using 块,那么该对象是在随后还是在发生垃圾收集时被丢弃?如果我在 using 块中调用 Dispose() 会发生什么?
using (MyDisposableObj mydispobj = new MyDisposableObj())
{
}
Stream 类(例如 BinaryWriter)有 Finalize 方法吗?我为什么要使用它?
解决方案
将处置与垃圾收集分开很重要。它们是完全不同的事物,但有一个共同点,我稍后会谈到。
Dispose
, 、垃圾收集和终结
当你写一个 using
语句,它只是 try/finally 块的语法糖,因此 Dispose
即使主体中的代码也会被调用 using
语句抛出异常。它 不 意味着该对象在块末尾被垃圾收集。
处置量约为 非托管资源 (非内存资源)。这些可以是 UI 句柄、网络连接、文件句柄等。这些资源是有限的,因此您通常希望尽快释放它们。你应该实施 IDisposable
每当您的类型“拥有”非托管资源时,无论是直接(通常通过 IntPtr
)或间接(例如通过一个 Stream
, , A SqlConnection
ETC)。
垃圾收集本身只与内存有关——有一点小小的不同。垃圾收集器能够找到不再被引用的对象,并释放它们。不过,它不会一直寻找垃圾 - 仅当它检测到需要时(例如如果堆的某一“代”内存不足)。
扭曲的是 定稿. 。垃圾收集器保留一个不再可访问的对象列表,但这些对象具有终结器(写为 ~Foo()
在 C# 中,有些令人困惑 - 它们与 C++ 析构函数完全不同)。它在这些对象上运行终结器,以防它们在释放内存之前需要进行额外的清理。
终结器几乎总是用于在类型的用户忘记以有序的方式处置资源的情况下清理资源。所以如果你打开一个 FileStream
但忘记打电话 Dispose
或者 Close
, ,终结器将 最终 为您释放底层文件句柄。在我看来,在一个编写良好的程序中,终结器几乎不应该触发。
将变量设置为 null
关于将变量设置为的一个小问题 null
- 垃圾收集几乎不需要这样做。如果它是一个成员变量,您有时可能会想要这样做,尽管根据我的经验,不再需要对象的“部分”的情况很少见。当它是局部变量时,JIT 通常足够聪明(在发布模式下),可以知道何时不再使用引用。例如:
StringBuilder sb = new StringBuilder();
sb.Append("Foo");
string x = sb.ToString();
// The string and StringBuilder are already eligible
// for garbage collection here!
int y = 10;
DoSomething(y);
// These aren't helping at all!
x = null;
sb = null;
// Assume that x and sb aren't used here
有一次它 可能 值得将局部变量设置为 null
是当您处于循环中时,循环的某些分支需要使用该变量,但您知道已经到达了不需要使用的点。例如:
SomeObject foo = new SomeObject();
for (int i=0; i < 100000; i++)
{
if (i == 5)
{
foo.DoSomething();
// We're not going to need it again, but the JIT
// wouldn't spot that
foo = null;
}
else
{
// Some other code
}
}
实现 IDisposable/终结器
那么,您自己的类型应该实现终结器吗?几乎可以肯定不是。如果你只 间接地 持有非托管资源(例如你有一个 FileStream
作为成员变量)然后添加您自己的终结器将无济于事:当您的对象符合垃圾回收条件时,该流几乎肯定有资格进行垃圾回收,因此您可以依赖 FileStream
有一个终结器(如果需要的话 - 它可能指的是其他东西,等等)。如果您想“几乎”直接持有非托管资源, SafeHandle
是你的朋友 - 相处需要一些时间,但这意味着你会 几乎 永远不需要再次编写终结器. 。如果您对资源有真正的直接处理(一个 IntPtr
),你应该寻找移动到 SafeHandle
你尽快做。(那里有两个链接 - 最好都阅读。)
乔·达菲有一个 关于终结器和 IDisposable 的非常长的指南 (与许多聪明人共同撰写)值得一读。值得注意的是,如果你密封你的课程,它会让生活变得更加轻松:压倒一切的模式 Dispose
调用一个新的虚拟 Dispose(bool)
方法等仅在您的类设计用于继承时才相关。
这有点啰嗦,但请要求澄清您想要的地方:)
其他提示
当你处理对象时,资源被释放。当您将空的变量,你只是改变一个参考。
myclass = null;
在执行此操作后,该对象MyClass的指的是仍然存在,并会继续,直到GC都绕来清除它。如果处置显式调用,或者它是在一个使用块,任何资源将被尽快释放。
在两个操作没有太多的做对方。 当您设置一个基准为null,它只是做到这一点。它本身并不影响被引用在所有的类。 您的可变根本不再指向对象时,它用于,但物体本身是不变的。
在调用Dispose(),它是对象本身上的方法调用。无论Dispose方法做,现在是在对象上完成的。但是,这并不影响你的参考对象。
重叠的唯一区域是当有一个对象的更多引用,就会最终得到垃圾收集。并且如果类实现IDisposable接口,然后处置()将被垃圾回收之前在对象上被调用。
但是,你参考设置为null后,有两个原因,不会立即发生。 首先,其他的引用可能存在,所以它不会被垃圾收集都还没有;第二,即使是最后一个参考,所以现在准备进行垃圾回收,什么都不会发生,直到垃圾回收器决定删除的对象。
调用的对象上的Dispose()不“杀死”以任何方式的对象。它通常用来清理,使该对象的可以的安全地后删除,但最终,没有什么神奇的有关Dispose,它只是一个类的方法。