析构函数应该只释放对象所持有的非托管资源,并且不应该引用其他对象。如果您只有托管引用,则不需要(也不应该)实现析构函数。您希望这仅用于处理非托管资源。 因为拥有析构函数需要一些成本,所以你应该只对消耗有价值的非托管资源的方法实现这一点。

     

- 用于C ++的 C#中的十大陷阱程序员

文章没有深入讨论这个问题,但在C#中使用析构函数会涉及哪些成本?

注意:我知道GC以及在可靠的时候没有调用析构函数的事实,除此之外,还有什么吗?

有帮助吗?

解决方案

任何具有终结器的对象(我更喜欢该术语而非析构函数,以强调与C ++析构函数的区别)都会添加到终结器队列中。这是一个对象的引用列表,这些对象具有在删除之前必须调用的终结器。

当对象用于垃圾收集时,GC将发现它在终结器队列中并将引用移动到可释放(f-reachable)队列。这是终结器后台线程依次调用每个对象的终结器方法的列表。

一旦调用了对象的终结器,该对象就不再位于终结器队列中,因此它只是GC可以删除的常规托管对象。

这意味着如果一个对象有一个终结器,它将至少存在一个垃圾收集,然后才能被删除。这通常意味着对象将被移动到下一个堆生成,这涉及实际将内存中的数据从一个堆移动到另一个堆。

其他提示

我最广泛的讨论看看这一切是如何运作的,由Joe Duffy完成。它有比你想象的更多的细节。

接下来,我把放在一起一种实用的方法,每天这样做 - 少花钱,但更多的是实施。

Guffa和JaredPar很好地介绍了细节,所以我只会添加一些关于终结器或析构函数的深奥注释,因为不幸的是C#语言规范称它们为。

要记住的一件事是,由于终结器线程按顺序运行所有终结器,因此终结器中的死锁将阻止所有剩余(和将来)的终结器运行。由于在终结器完成之前不会收集这些实例,因此死锁的终结器也会导致内存泄漏。

本文详细介绍了该问题。在一篇简单的SO帖子中总结起来真的很难: http:// msdn。 microsoft.com/en-us/magazine/bb985010.aspx

Guffa 已经很好地总结了终结器成本中的因素。最近有一篇关于Java中终结器成本的文章,这也提供了一些见解。

使用GC.SuppressFinalize从终结器队列中删除对象,可以避免.net中的部分成本。我根据文章在.net上运行了一些快速测试并将其发布到这里(虽然Java侧面的重点更多)。


下面是结果图 - 它没有真正的最佳标签;-)。 "调试=真/假"是指空的简单终结者:

~ConditionalFinalizer()  
{  
    if (DEBUG)  
    {  
        if (!resourceClosed)  
        {  
            Console.Error.WriteLine("Object not disposed");  
        }  
        resourceClosed = true;  
    }  
} 

"禁止=真QUOT;是指在Dipose方法中是否调用了GC.SuppressFinalize。 点击 结果

摘要

对于.net,通过调用GC.SuppressFinalize从终结器队列中删除对象是将对象留在队列上的一半成本。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top