我已经读过在C#中处理对象/ IDisposable接口和析构函数,但对我来说它们似乎做同样的事情?
两者有什么区别?为什么我会使用一个而不是另一个?实际上,在此示例(下面的链接)中,此代码使用IDisposable接口和析构函数:
http://msdn.microsoft.com/en-us/library/system.idisposable.aspx
评论说析构函数是否使用了终结代码,但我如何决定何时使用其中一个?
我写了一篇相当深入的文章,该文章应该有助于解释终结者,IDisposable以及何时应该使用其中一个:http://gregbee.ch/blog/implementing-and-using-the-idisposable-interface
可能最相关的部分引用如下:
当您使用非托管资源(如句柄和数据库连接)时,应使用"获取延迟"和"提前释放"原则确保它们保持最短时间.在C++中,释放资源通常在析构函数中完成,析构函数在删除对象时确定性地运行.但是,.NET运行时使用垃圾收集器(GC)来清理和回收不再可访问的对象所使用的内存; 因为它定期运行,这意味着清理对象的点是不确定的.这样做的结果是托管对象不存在析构函数,因为没有确定的位置来运行它们.
C#具有终结器,而不是析构函数,它通过覆盖在基类Object类上定义的Finalize方法来实现(尽管C#有点令人困惑地使用C++析构函数语法~Object).如果一个对象覆盖了Finalize方法,那么当GC超出范围时,它不会被GC收集,而是将GC放在终结器队列中.在下一个GC循环中,队列上的所有终结器都会运行(在当前实现中的单个线程上),并且回收已完成对象的内存.很明显,为什么你不想在终结器中进行清理:它需要两个GC周期来收集对象而不是一个,并且有一个线程,其中所有终结器都在运行,而每个其他线程都被挂起,所以这会伤害表现.
因此,如果您没有析构函数,并且您不希望将清理留给终结器,那么唯一的选择是手动确定地清理对象.输入IDisposable接口,该接口提供支持此功能的标准,并定义一个方法Dispose,您可以在其中放置对象的清理逻辑.在finally块中使用时,此接口提供与析构函数等效的功能.代码中finally块的原因主要是支持IDisposable接口; 这就是为什么C++只使用try/except,因为不需要带有析构函数的finally块.
终结器使您有机会处理非托管资源,以防对象用户忘记呼叫IDisposable.Dispose
.
如果您的对象实现IDisposable
,则对象的用户必须调用.Dispose
.你不必有清理用户的混乱; 但这是一件好事.
我在Stackoverflow上最受欢迎的答案从一开始就引导你为什么你有IDisposable,它应该做什么,你的终结器可以做什么,它不应该做什么.
这个答案融化了面孔
已被用来形容它:P
在托管编程语言中使用析构函数(~Object())是最愚蠢的想法.对于像C,C++这样的非托管语言来说,使用RAII惯用语来构建析构函数非常有意义,但对于像Java,C#这样的托管来说,这是非常荒谬的.
Java Collection Framework的前任项目负责人Joshua Bloch已经指出,Java中的finalize()方法(相当于C#的C++,如析构函数)的想法是有史以来最大的错误.与C#相同,Java中的finallize()为"new"提供了开销,因为它必须在分配期间添加到finallizer队列.更重要的是,垃圾收集器必须在队列中弹出并运行finallize(),因此gc期间的开销是两倍.
C#有许多增强功能,如"using(IDisposable){}",它不仅允许将IDisposable变量限制在"using"块的范围内,而且还保证它的清理.我的问题是,为什么C#遵循相同的Java路径导致了很大的错误.可能如果dotnet的开发在2003〜2005年左右开始,当时许多Java架构师发现了finallize()的谬误,那么错误就会被阻止.
一种语言的许多好主意通常转移到其他语言,如C#中的"IDisposable/using combo",它在"try(object-to-dispose){}"语句中转移到Java 1.7.但是它太糟糕了,以至于语言架构师在从一个转移到另一个的过程中没有发现伪装成好主意的坏主意.
我的建议是永远不要使用~Destructor()并坚持使用IDisposable/using combo,如果你需要手动清理非托管资源,比如数据库连接.