何时应该使用GC.SuppressFinalize()?

382

在.NET中,什么情况下应该使用GC.SuppressFinalize()

使用这种方法有哪些优势?


我看到了一些关于finalizers和IDisposable的问题,stackoverflow上也应该有关于GC.SupressFinalize和弱引用的内容。 - Sam Saffron
我认为弱引用在终结器方面并没有什么作用 - 也许你应该发一个更直接的问题来询问它们。 - Michael Burr
是的,我本来想发布一个关于弱引用的单独问题,当你构建对象池时,所有这些都可以联系在一起。此外,我应该问一个关于对象复活的问题,比如重新注册终结处理程序。 - Sam Saffron
5个回答

387

SuppressFinalize方法只能被具有终结器的类调用。该方法向垃圾回收器(GC)指示this对象已完全清理。

当您拥有一个终结器时,建议使用IDisposable模式:

public class MyClass : IDisposable
{
    private bool disposed = false;

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // called via myClass.Dispose(). 
                // OK to use any private object references
            }
            // Release unmanaged resources.
            // Set large fields to null.                
            disposed = true;
        }
    }

    public void Dispose() // Implement IDisposable
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    ~MyClass() // the finalizer
    {
        Dispose(false);
    }
}

通常情况下,在创建对象时,CLR会跟踪带有终结器的对象(使它们更昂贵)。SuppressFinalize 告诉GC对象已经被正确清理,不需要进入终结器队列。它看起来像是C++的析构函数,但却不像。

SuppressFinalize 优化并不是微不足道的,因为您的对象可能会在等待终结器队列时存活很长时间。请勿试图在其他对象上调用 SuppressFinalize,这是一种严重的缺陷,可能导致问题发生。

设计指南告诉我们,如果您的对象实现了 IDisposable,则不需要使用终结器,但如果您有一个终结器,则应该实现 IDisposable以允许确定性清理您的类。

大多数情况下,您应该能够通过 IDisposable 清除资源。只有当您的对象持有非托管资源并且需要保证这些资源得到清理时,您才需要使用终结器。

注意:有时,程序员会向自己的 IDisposable 类的调试版本添加终结器,以测试代码是否已正确处理其 IDisposable 对象。

public void Dispose() // Implement IDisposable
{
    Dispose(true);
#if DEBUG
    GC.SuppressFinalize(this);
#endif
}

#if DEBUG
~MyClass() // the finalizer
{
    Dispose(false);
}
#endif

2
在第一个代码片段中,我只是展示了推荐的IDisposable + finalizer模式。调试代码很好,但它可能会分散注意力。.. 我只能建议避免使用finalizers,除非是针对具有非托管资源的类。编写安全的finalizer代码并不容易。 - Robert Paulson
3
为什么我们需要在终结器中以false作为参数调用dispose方法?如果dispose从未被调用,那怎么办?如果我们只是检查对象是否已被处理并执行实际的清理操作呢? - Dreamer
6
@Dreamer - 这取决于你的实现方式。一般来说,你需要知道Dispose方法是被终结器调用还是IDisposable.Dispose()方法实现调用的。如果是从终结器调用,你必须假设私有引用已经无效,你真的不能做太多事情。但是如果是从IDisposable.Dispose()方法实现调用,你就知道引用仍然有效。 - Robert Paulson
61
如果实现 IDisposable 接口的类没有声明为 sealed,那么即使它不包括自定义终结器,也应该包含调用 GC.SuppressFinalize(this) 的代码。这是为了确保对于添加自定义终结器但仅覆盖受保护的 Dispose(bool) 方法的派生类型具有正确的语义。 - Sam Harwell
4
对于派生类来说,像@SamHarwell提到的那样不被“密封”很重要。如果类没有被密封,CodeAnalysis结果将出现ca1816 + ca1063,但是没有被密封的类在没有“SuppressFinalize”的情况下也可以正常运行。 - dashesy
显示剩余7条评论

52
SupressFinalize告诉系统,不需要调用终结器因为在终结器中需要完成的工作已经完成。从.NET文档中得知:

实现IDisposable接口的对象可以在IDisposable.Dispose方法中调用此方法,以防止垃圾回收器在不需要它的对象上调用Object.Finalize。

通常情况下,大多数的Dispose()方法都应该能够调用GC.SupressFinalize(),因为它应该清理掉终结器中需要清理的所有内容。

SupressFinalize仅提供了一种优化方式,允许系统不必将对象排队到终结器线程中。一个正确编写的Dispose()和终结器应该能够正常工作,无论是否调用了GC.SupressFinalize()


2
一个正确编写的Dispose()/finalizer应该能够在调用GC.SupressFinalize()时正常工作,也可以在没有调用它的情况下正常工作。出于好奇,现在(2022/C# 10+)为什么建议使用SupressFinalize呢? - user1945782

6
Dispose(true);
GC.SuppressFinalize(this);

如果一个对象具有终结器,.net会将其引用放入终结队列。

由于我们调用了Dispose(true),它会清除对象,因此我们不需要终结队列来完成这个工作。

因此,调用GC.SuppressFinalize(this)会删除终结队列中的引用。


3
在我看来,这是一种反模式。最好的情况是它属于过早优化的范畴。你的系统是否存在 finalisation 队列瓶颈?不太可能。如果做错了会有性能问题吗?会的。所以“治疗”比疾病更糟糕。 - Liam

2
如果一个类或其派生类可能持有具有终结器的对象的最后一个活动引用,则在可能受到该终结器影响的任何操作之后,应在对象上调用GC.SuppressFinalize(this)GC.KeepAlive(this),以确保终结器直到该操作完成后才运行。
在没有终结器的任何类中,GC.KeepAlive()GC.SuppressFinalize(this)的成本基本相同。而具有终结器的类通常应该调用GC.SuppressFinalize(this),因此在Dispose()的最后一步使用后者函数可能并不总是必要,但也不会有错。

-1

10
我认为“必须”是错误的,甚至不应该使用“应该”。只是在某些情况下,你可以消除排队/完成对象的开销。 - Basic

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接