C#析构函数(又名终结器)涉及的成本?
析构函数应该只释放对象所持有的非托管资源,而不应该引用其他对象。如果只有托管引用,则不需要(也不应该)实现析构函数。您只需要在处理非托管资源时使用此选项因为使用析构函数会有一些成本,所以您应该只在消耗有价值的非托管资源的方法上实现它 -- 本文没有更深入地探讨这一点,但是在C#中使用析构函数会带来哪些成本 注意:我知道GC和析构函数没有在可靠时间调用的事实,除此之外,还有其他事情吗?。它的细节比你想象的要多C#析构函数(又名终结器)涉及的成本?,c#,idisposable,destructor,finalizer,C#,Idisposable,Destructor,Finalizer,析构函数应该只释放对象所持有的非托管资源,而不应该引用其他对象。如果只有托管引用,则不需要(也不应该)实现析构函数。您只需要在处理非托管资源时使用此选项因为使用析构函数会有一些成本,所以您应该只在消耗有价值的非托管资源的方法上实现它 -- 本文没有更深入地探讨这一点,但是在C#中使用析构函数会带来哪些成本 注意:我知道GC和析构函数没有在可靠时间调用的事实,除此之外,还有其他事情吗?。它的细节比你想象的要多 接下来,我每天都要做这件事——少谈成本,而要关注实现。 < P>有终结器的对象(我更喜欢
接下来,我每天都要做这件事——少谈成本,而要关注实现。
< P>有终结器的对象(我更喜欢那个术语,而不是析构函数,强调C++析构函数的差异)被添加到终结器队列中。这是对具有终结器的对象的引用列表,在删除这些对象之前必须调用终结器 当对象进行垃圾收集时,GC将发现它在终结器队列中,并将引用移动到freachable(f-reachable)队列。这是终结器后台线程依次调用每个对象的终结器方法的列表 一旦调用了对象的终结器,该对象就不再位于终结器队列中,因此它只是GC可以删除的常规托管对象这一切都意味着,如果一个对象有一个终结器,它将在至少一次垃圾收集之后才能被删除。这通常意味着对象将被移动到下一代堆,这涉及到将内存中的数据从一个堆移动到另一个堆。Guffa和JaredPar很好地涵盖了细节,因此我只需添加一个关于终结器或析构函数的有点深奥的注释,因为C语言规范不幸地调用了它们 需要记住的一点是,由于终结器线程按顺序运行所有终结器,终结器中的死锁将阻止所有剩余(和未来)终结器运行。由于这些实例在其终结器完成之前不会收集,因此死锁的终结器也会导致内存泄漏。总结了终结器成本中的因素。最近有一篇关于Java中终结器的成本的文章,也提供了一些见解 通过使用GC.SuppressFinalize从终结器队列中删除对象,可以避免.net中的部分开销。我根据这篇文章在.net中运行了一些快速测试并发布了它(尽管重点更多地放在Java方面)
下面是一个结果图表-它实际上没有最好的标签;-)。“Debug=true/false”指的是空的vs简单终结器:
~ConditionalFinalizer()
{
if (DEBUG)
{
if (!resourceClosed)
{
Console.Error.WriteLine("Object not disposed");
}
resourceClosed = true;
}
}
“Suppress=true”指是否在Dipose方法中调用了GC.SuppressFinalize。
总结 对于.net,通过调用GC.SuppressFinalize从终结器队列中删除对象的成本是将对象留在队列中的成本的一半
如果我来自任何地方,我来自C,尽管我对C的精通程度远远超过这两种语言,但我在C中搜索析构函数信息时刚刚找到了这篇文章。实际上,C#3语言规范仍然将它们称为析构函数,所以即使是强硬的,他们也不像他们的C++同行,但不幸的是,他们仍然共享这个名字。事实上,除了极少数例外,只有主要目的围绕着终结的类才应该拥有它们。需要最终确定的非托管资源(职责)不应该由包含其他内容的类持有;每个人都应该被转移到一个班级里,这个班级的主要目的是保持它并确保它的清洁(确保它的职责得到履行)。由于可终结对象直接或间接引用的所有内容都必须保留到终结器运行为止,因此可终结类应尽可能轻量级。我预计,我的大多数对象只会在应用程序生命周期结束时被拉下并释放,这会产生什么影响?GC将一直运行,直到清除所有内容,不是吗?当应用程序结束时,GC将允许终结器运行一段时间,但最终它将只拆除堆,即使所有对象都未终结。如果你需要一些清理代码来运行,你应该改为实现IDisposable接口,它可以让你控制对象的生命周期。只是强调一下,当应用程序关闭时,.NET只允许终结器运行固定的时间。(我认为目前每个终结器的最大值为10秒,所有终结器的最大值为30秒。这意味着不能保证所有终结器都能运行)