C# 析构函数与 IDisposable?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/456213/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Destructor vs IDisposable?
提问by GurdeepS
I've read about disposing objects/IDisposable interface and destructors in C#, but to me they seem to do the same thing?
我读过关于在 C# 中处理对象/IDisposable 接口和析构函数的文章,但对我来说它们似乎做同样的事情?
What is the difference between the two? Why would I use one over the other? In fact, in this example (link below) this code uses both the IDisposable interface and a destructor:
两者有什么区别?为什么我要使用一个?事实上,在这个例子(下面的链接)中,这段代码同时使用了 IDisposable 接口和一个析构函数:
http://msdn.microsoft.com/en-us/library/system.idisposable.aspx
http://msdn.microsoft.com/en-us/library/system.idisposable.aspx
The comment says the destructor is if the finalization code is not used, but how do I decide when to use one over the other?
评论说析构函数是如果没有使用终结代码,但是我如何决定何时使用一个而不是另一个?
回答by Greg Beech
I wrote a fairly in-depth post which should help to explain about finalizers, IDisposable, and when you should use one or the other: http://gregbee.ch/blog/implementing-and-using-the-idisposable-interface
我写了一篇相当深入的文章,它应该有助于解释终结器、IDisposable 以及何时应该使用一个或另一个:http: //gregbee.ch/blog/implementing-and-using-the-idisposable-interface
Probably the most relevant part is quoted below:
可能最相关的部分引用如下:
When you are using unmanaged resources such as handles and database connections, you should ensure that they are held for the minimum amount of time, using the principle of acquire late and release early. In C++ releasing the resources is typically done in the destructor, which is deterministically run at the point where the object is deleted. The .NET runtime, however, uses a garbage collector (GC) to clean up and reclaim the memory used by objects that are no longer reachable; as this runs on a periodic basis it means that the point at which your object is cleaned up is nondeterministic. The consequence of this is that destructors do not exist for managed objects as there is no deterministic place to run them.
Instead of destructors, C# has finalizers which are implemented by overriding the Finalize method defined on the base Object class (though C# somewhat confusingly uses the C++ destructor syntax ~Object for this). If an object overrides the Finalize method then rather than being collected by the GC when it is out of scope, the GC places it on a finalizer queue. In the next GC cycle all finalizers on the queue are run (on a single thread in the current implementation) and the memory from the finalized objects reclaimed. It's fairly obvious from this why you don't want to do clean up in a finalizer: it takes two GC cycles to collect the object instead of one and there is a single thread where all finalizers are run while every other thread is suspended, so it's going to hurt performance.
So if you don't have destructors, and you don't want to leave the cleanup to the finalizer, then the only option is to manually, deterministically, clean up the object. Enter the IDisposable interface which provides a standard for supporting this functionality and defines a single method, Dispose, where you put in the cleanup logic for the object. When used within a finally block, this interface provides equivalent functionality to destructors. The reason for finally blocks in code is primarily to support the IDisposable interface; this is why C++ uses simply try/except as there is no need for a finally block with destructors.
当您使用句柄和数据库连接等非托管资源时,您应该使用延迟获取和提前释放的原则确保它们被持有的时间最短。在 C++ 中,释放资源通常在析构函数中完成,该析构函数在对象被删除时确定性地运行。但是,.NET 运行时使用垃圾收集器 (GC) 来清理和回收不再可访问的对象所使用的内存;由于这会定期运行,这意味着清理对象的时间点是不确定的。这样做的结果是托管对象不存在析构函数,因为没有确定性的位置来运行它们。
C# 没有析构函数,而是通过覆盖在基础 Object 类上定义的 Finalize 方法实现的终结器(尽管 C# 对此使用 C++ 析构函数语法 ~Object 有点令人困惑)。如果一个对象覆盖了 Finalize 方法,那么当它超出范围时不会被 GC 收集,而是 GC 将它放在终结器队列中。在下一个 GC 周期中,队列中的所有终结器都将运行(在当前实现中的单个线程上)并回收来自终结对象的内存。从这里很明显为什么你不想在终结器中进行清理:它需要两个 GC 周期来收集对象而不是一个,并且有一个线程,所有终结器都在运行,而其他所有线程都被挂起,所以它会损害性能。
因此,如果您没有析构函数,并且不想将清理工作留给终结器,那么唯一的选择就是手动、确定性地清理对象。进入 IDisposable 接口,该接口提供了支持此功能的标准,并定义了一个方法 Dispose,您可以在其中放置对象的清理逻辑。当在 finally 块中使用时,此接口提供与析构函数等效的功能。代码中finally块的原因主要是为了支持IDisposable接口;这就是为什么 C++ 只使用 try/except,因为不需要带有析构函数的 finally 块。
回答by Ian Boyd
Short version
精简版
A finalizer gives you an opportunityto dispose of unmanaged resources in case the user of your object forgot to call IDisposable.Dispose
.
终结器使您有机会处理非托管资源,以防对象的用户忘记调用IDisposable.Dispose
.
If your object implements IDisposable
, the user of your object mustcall .Dispose
. You don't haveto clean up the user's mess; but it's a nice thing to do.
如果您的对象实现了IDisposable
,则您的对象的用户必须调用.Dispose
. 你不必有清理用户的混乱; 但这是一件好事。
My most popular answer on Stackoverflowwalks you from the beginning why you have IDisposable, what it should do, what your finalizer can do, what it shouldn't do.
我在 Stackoverflow 上最受欢迎的答案从一开始就告诉您为什么要使用 IDisposable,它应该做什么,您的终结器可以做什么,不应该做什么。
This answer melts faces
这个答案融化了脸
has been used to describe it :P
已被用来描述它:P
回答by David Lee
Having destructor (~Object()) in managed programming language is the most dummest idea. It perfectly make sense for unmanaged languages like C,C++ to have destructors as they use RAII idiom but for managed like Java,C#, just so absurd.
在托管编程语言中使用析构函数 (~Object()) 是最愚蠢的想法。像 C、C++ 这样的非托管语言在使用 RAII 惯用语时拥有析构函数是完全有意义的,但对于像 Java、C# 这样的托管语言来说,这太荒谬了。
It has been pointed out by Joshua Bloch, a former project lead in Java Collection Framework, that the idea of finalize() method (which is equivalent to C#'s C++ like destructor) in Java was the biggest mistake ever made. Same as C#, finallize() in Java gives overhead to "new" as it must be added to the finallizer queue during allocation. More over, Garbage Collector must pop and run finallize() in the queue, so twice the overhead during gc.
Java Collection Framework 的前项目负责人 Joshua Bloch 指出,Java 中的 finalize() 方法(相当于 C# 的 C++ 类析构函数)的想法是有史以来犯的最大错误。与 C# 相同,Java 中的 finallize() 为“new”提供了开销,因为它必须在分配期间添加到终结器队列中。更重要的是,垃圾收集器必须在队列中弹出并运行 finallize(),因此 gc 期间的开销是两倍。
C# had many enhanced features like "using(IDisposable) {}" which not only allow the IDisposable variable to be confined to the scope of "using" block but also guarantee it's cleanup. My question is, why did C# follow the same trail of Java which lead to great mistake. May be if the development of dotnet started after around 2003 ~ 2005, when many Java architects found the fallacy of finallize(), then the mistake would have been prevented.
C# 有很多增强的特性,比如“using(IDisposable){}”,它不仅允许 IDisposable 变量被限制在“using”块的范围内,而且还保证它的清理。我的问题是,为什么 C# 遵循 Java 的相同轨迹,导致了巨大的错误。可能如果 dotnet 的开发是在 2003~2005 年左右开始的,那时很多 Java 架构师发现了 finallize() 的谬误,那么这个错误就可以避免了。
Many good idea of one language is often transferred to other language like the "IDisposable/using combo " in C# which was transferred to Java 1.7 in its "try(object-to-dispose) {}" statement. But its too bad that language architects fail to discover the bad idea disguised as good idea during it's transfer from one to the other.
一种语言的许多好主意经常转移到其他语言,例如 C# 中的“IDisposable/using 组合”,它在其“try(object-to-dispose) {}”语句中转移到 Java 1.7。但遗憾的是,语言架构师未能在从一个转移到另一个的过程中发现伪装成好主意的坏主意。
My advise is never to use ~Destructor() and stick with IDisposable/using combo if you need to manually cleanup the unmanaged resource like database connections.
如果您需要手动清理非托管资源(如数据库连接),我的建议是永远不要使用 ~Destructor() 并坚持使用 IDisposable/using 组合。