IDisposable.Dispose()应该被设计成可以安全地多次调用吗?

59

IDisposable的实现是否应该使Dispose()方法可以多次调用?还是相反?大多数.NET Framework类采取哪种方法?

具体来说,System.Data.Linq.DataContext.Dispose()方法是否可以安全地多次调用?

我提出这个问题的原因是想知道是否需要进行额外的保护:

public override void Dispose(bool disposing)
{
    // Extra protection...
    if (this.obj != null)
    {
        this.obj.Dispose();
        this.obj = null;
    }

    // Versus simply...
    this.obj.Dispose();

    base.Dispose(disposing);
}

在处理一个类的IDisposable成员时,我是否应该检查其之前是否已被调用过,或者只需调用this.obj.Dispose()而不必关心它之前是否已经被调用。


我更喜欢使用 if (!= null) 的语法。你还应该使用disposing标志来保护聚合对象的释放。 - Ritch Melton
1
如果您的对象还具有终结器,请确保仅在 'disposing' 为 false 时调用 this.obj.Dispose()。否则,您可能会在终结器内部访问另一个对象,该对象可能已经完成了终结(终结顺序是未定义的,并且对于循环引用没有安全的终结顺序)。如果我是微软,我会将 'disposing' 标志重命名为 'disposedExplicitly' 或 'notInFinalizer' :) - Cygon
4个回答

74

虽然你可以安全地多次调用它,但如果可能的话最好避免。

根据IDisposable.Dispose()的MSDN页面说明:

如果一个对象的Dispose方法被多次调用,那么在第一次调用后,该对象必须忽略所有后续的调用。如果Dispose方法被多次调用,该对象不能抛出异常。


7
是的,您实现的IDisposable.Dispose()应该能够容忍多次调用。在第一次Dispose()之后,所有其他调用都可以立即返回。
我更喜欢您代码示例的前半部分,逐步处理和释放本地变量。
请注意,即使您在代码中实现了Dispose和null模式,.Dispose()也可能会被多次调用。如果多个消费者持有对同一可处置对象的引用,则该对象的Dispose将可能被多次调用,因为这些消费者放弃对它的引用。

2

对象应该能够容忍多次调用Dispose,因为在异常情况下,清理代码可能很难确定哪些东西已经被清理了,哪些没有。在清理IDisposable字段时将其置空(并且对已经为空的字段也要宽容)可以更轻松地避免重复调用Dispose,但是让对象能够容忍多次释放实际上并不会耗费任何代价,并且有助于避免某些抛出异常的尴尬情况。


-2
如果一个对象已经被处理,你不应该再次处理它。这可以帮助你避免在垃圾回收器中延长对象的生命周期。
我通常使用的模式是这样的。
// A base class that implements IDisposable.
// By implementing IDisposable, you are announcing that
// instances of this type allocate scarce resources.
public class BaseClass: IDisposable
{
    /// <summary>
    /// A value indicating whether this instance of the given entity has 
    /// been disposed.
    /// </summary>
    /// <value>
    /// <see langword="true"/> if this instance has been disposed; otherwise, 
    /// <see langword="false"/>.
    /// </value>
    /// <remarks>
    /// If the entity is disposed, it must not be disposed a second
    /// time. The isDisposed field is set the first time the entity
    /// is disposed. If the isDisposed field is true, then the Dispose()
    /// method will not dispose again. This help not to prolong the entity's
    /// life in the Garbage Collector.
    /// </remarks>
    private bool isDisposed;

   /// <summary>
    /// Disposes the object and frees resources for the Garbage Collector.
    /// </summary>
    public void Dispose()
    {
        this.Dispose(true);

        // This object will be cleaned up by the Dispose method.
        // Therefore, you should call GC.SupressFinalize to
        // take this object off the finalization queue 
        // and prevent finalization code for this object
        // from executing a second time.
        GC.SuppressFinalize(this);
    }

    /// <summary>
    /// Disposes the object and frees resources for the Garbage Collector.
    /// </summary>
    /// <param name="disposing">If true, the object gets disposed.</param>
    protected virtual void Dispose(bool disposing)
    {
        if (this.isDisposed)
        {
            return;
        }

        if (disposing)
        {
            // Dispose of any managed resources here.

        }

        // Call the appropriate methods to clean up
        // unmanaged resources here.
        // Note disposing is done.
        this.isDisposed = true;

    }

    // Use C# destructor syntax for finalization code.
    // This destructor will run only if the Dispose method
    // does not get called.
    // It gives your base class the opportunity to finalize.
    // Do not provide destructors in types derived from this class.
    ~BaseClass()
    {
        // Do not re-create Dispose clean-up code here.
        // Calling Dispose(false) is optimal in terms of
        // readability and maintainability.
        Dispose(false);
    }      
}

4
重复释放对象如何延长对象的生命周期?垃圾回收器只关心是否还有引用指向该对象。此外,您的代码不必要地调用了GC.SuppressFinalize(),声称对象在终结队列中,但实际上并不在,因为它没有终结器。GC不会以任何方式查找IDisposable(这本身也是危险的,因为显式Dispose()可以访问其他对象的方法,而终结器不应该这样做)。 - Cygon
@Cycon:我意识到在你的评论中我没有发布足够完整的代码示例。通过延长生命周期,我的意思是它将在终止队列中持久存在。我已经更新了代码,添加了终止代码并修复了我发现的另一个小错误。 - James South
6
请注意,此模式仅应在实际处理未受管资源且具有可执行逻辑而无需在代码中引用任何其他托管实例的情况下使用。如果您只处理需要清理的其他托管IDisposable实例(或者您正在使用IDisposable以某种语言便利方式,如using块),则不应对对象进行任何终结器处理。仅仅拥有一个终结器会改变GC收集对象的方式(它必须被放入终结器队列)。 - Adam Robinson

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