【问题标题】:Should IDisposable.Dispose() be made safe to call multiple times?是否应该使 IDisposable.Dispose() 可以安全地多次调用?
【发布时间】:2011-03-15 02:26:55
【问题描述】:

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 标志来保护聚合对象的 dispose。
  • 如果您的对象也有终结器,请确保仅在 'disposing' 为 false 时调用 this.obj.Dispose()。否则,您可能正在从终结器中访问另一个对象,该对象可能已经终结(终结顺序未定义,循环引用没有安全的终结顺序)。如果我是 Microsoft,我会将“disposing”标志重命名为“disposedExplicitly”或“notInFinalizer”:)

标签: c# .net idisposable


【解决方案1】:

你应该安全地多次调用它,但如果可以的话,你应该避免它。

来自IDisposable.Dispose() 上的 MSDN 页面:

如果多次调用对象的 Dispose 方法,则对象必须忽略第一次调用之后的所有调用。如果多次调用其 Dispose 方法,则该对象不得引发异常。

【讨论】:

    【解决方案2】:

    是的,您的 IDisposable.Dispose() 实现应该允许被多次调用。在第一次调用 Dispose() 之后,所有其他调用都可以立即返回。

    我更喜欢您的代码示例的第一部分,即在您执行过程中处理局部变量并将其设为空。

    请注意,即使您在代码中实现 Dispose 和 null 模式,您的 .Dispose() 也可能会被多次调用。如果多个消费者持有对同一个一次性对象的引用,则该对象的 Dispose 可能会被多次调用,因为这些消费者删除了对它的引用。

    【讨论】:

      【解决方案3】:

      对象应该容忍多次调用 Dispose,因为——尤其是在存在异常的情况下——清理代码可能很难确定哪些东西已经被清理,哪些没有。在清理 IDisposable 字段时将其清空(并容忍已经为空的字段)将更容易避免对 Dispose 的冗余调用,但让对象容忍多次处理实际上并不需要任何成本,而且它有助于在一些抛出异常的情况下避免恶心。

      【讨论】:

        【解决方案4】:

        如果一个对象被丢弃,你不应该再次丢弃它。这有助于你不延长垃圾收集器中对象的寿命。

        我通常使用的模式是这样的。

        // 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);
            }      
        }
        

        【讨论】:

        • 第二次处理对象如何延长对象的生命周期?垃圾收集器所关心的只是它是否有引用。此外,您的代码不必要地调用 GC.SuppressFinalize(),声称该对象在终结队列中,但事实并非如此,因为它没有终结器。 GC 不会以任何方式查找 IDisposable(这无论如何都会很危险,因为显式 Dispose() 可以访问其他对象上的方法,而终结器不应该这样做)。
        • @Cycon:我意识到我没有在您的 cmets 之后发布足够完整的代码示例。通过延长寿命,我的意思是它将持续存在于完成队列中。我已经更新以添加终结代码并修复我发现的另一个小错误。
        • 请注意,这种模式应该只在您实际处理非托管资源并且您有可以执行的逻辑而无需调用引用代码中的任何其他托管实例的情况下使用。如果您要处理的只是其他需要清理的托管 IDisposable 实例(或者您正在使用 IDisposable 以实现某种语言便利,例如 using 块),那么您不应该做任何事情带有终结器。只是在你的对象上有一个终结器会改变 GC 收集它的方式(它必须被放入终结器队列中)。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-07-15
        • 1970-01-01
        • 2018-11-18
        • 2015-05-15
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多