与C#析构函数相关的成本(又名:终结者)?

时间:2009-03-05 01:36:24

标签: c# idisposable destructor finalizer

  

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

     

- Top Ten Traps in C# for C++ Programmers

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

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

5 个答案:

答案 0 :(得分:8)

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

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

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

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

答案 1 :(得分:6)

The most extensive discussion I've seen on how this all works was done by Joe Duffy。它有比你想象的更多的细节。

在此之后,我将a practical approach放在一起进行日常工作 - 少花钱,但更多关于实施。

答案 2 :(得分:3)

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

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

答案 3 :(得分:2)

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

答案 4 :(得分:0)

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

使用GC.SuppressFinalize从终结器队列中删除对象,可以避免.net中的部分成本。我根据文章在.net上运行了一些快速测试并发布了它here(虽然重点更多地放在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从终结器队列中删除对象是将对象留在队列上的一半成本。