小编典典

将对象设置为null与Dispose()

c#

我对CLR和GC的工作方式着迷(我正在通过C#,Jon Skeet的书/帖子等阅读CLR来扩展我的知识)。

无论如何,说:

MyClass myclass = new MyClass();
myclass = null;

还是通过使MyClass实现IDisposable和析构函数并调用Dispose()来实现?

另外,如果我有一个带有using语句的代码块(例如,下面),如果我单步执行代码并退出using块,那么该对象是否已被丢弃,或者在发生垃圾回收时被丢弃了?如果我在using块中调用Dispose()会怎样?

using (MyDisposableObj mydispobj = new MyDisposableObj())
{

}

流类(例如BinaryWriter)是否具有Finalize方法?我为什么要使用它?


阅读 508

收藏
2020-05-19

共1个答案

小编典典

将处理与垃圾收集分开很重要。它们是完全独立的事物,有一点共同点,我将在稍后讨论。

Dispose ,垃圾收集和完成

当您编写一条using语句时,它只是try / finally块的语法糖,因此Dispose即使using语句主体中的代码引发异常也将被调用。这
并不 意味着对象是在块末尾被垃圾回收的。

处置是关于 非托管资源
(非内存资源)的。这些可能是UI句柄,网络连接,文件句柄等。这些都是有限的资源,因此您通常希望尽快释放它们。你应该执行IDisposable,只要你喜欢的类型“拥有”一个非托管资源,直接(通常通过IntPtr)或间接(例如,通过Stream中,SqlConnection等)。

垃圾回收本身仅与内存有关,只需要一点点扭曲即可。垃圾收集器能够找到不再被引用的对象,并释放它们。但是,它并不会一直在寻找垃圾-
仅当它检测到需要垃圾时(例如,如果堆的“一代”用完了内存)。

转折是 定案 。垃圾收集器会保留一个不再可访问的对象列表,这些对象具有终结器(用~Foo()C#编写,有些令人困惑-它们与C
++析构函数完全不同)。它在这些对象上运行终结器,以防万一它们需要在释放内存之前进行额外的清理。

在该类型的用户忘记按顺序处理它的情况下,几乎总是使用终结器来清理资源。因此,如果您打开a FileStream但忘记调用Disposeor
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 / finalizer

那么,您自己的类型是否应该实现终结器?几乎可以肯定。如果您仅 间接
持有非托管资源(例如,您拥有一个FileStream作为成员变量的资源),那么添加自己的终结器将无济于事:当您的对象成为对象时,流几乎肯定可以进行垃圾回收,因此您可以依靠FileStream具有终结器(如有必要,它可以引用其他内容,等等)。如果您想直接“几乎”持有一个非托管资源,那SafeHandle是您的朋友-
花费一些时间才能开始工作,但这意味着您几乎
不再需要编写终结器。如果您对资源(IntPtr)拥有真正直接的句柄,并且通常应该转向SafeHandle你尽快做。(那里有两个链接-最好同时阅读。)

乔·达菲(Joe Duffy)关于终结器和IDisposable(与许多聪明的人共同编写)有很长的指南集,值得阅读。值得注意的是,如果密封您的类,则可以使工作变得更加轻松:Dispose调用新虚拟Dispose(bool)方法等的重写模式仅在您的类设计为继承时才有意义。

这有点混乱,但是请澄清您想要的地方:)

2020-05-19