Должен ли IDisposable.Dispose() быть безопасным звонить несколько раз?

Если реализации 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(), не заботясь о том, был ли он ранее вызван.

Ответ 1

Вы должны быть в безопасности, чтобы называть его более одного раза, хотя вам, вероятно, следует избегать его, если сможете.

На странице MSDN IDisposable.Dispose():

Если объект Dispose method вызывается более одного раза, объект должен игнорировать все вызовы после первого. Объект не должен генерировать исключение, если его метод Dispose вызывается несколько раз.

Ответ 2

Да, ваши реализации IDisposable.Dispose() должны допускать многократные вызовы. После первого вызова Dispose() все остальные вызовы могут сразу вернуться.

Я бы предпочел первую часть вашего примера кода, чтобы уничтожить и обнулить локальные переменные во время вашей работы.

Помните, что ваш .Dispose() может вызываться несколько раз, даже если вы реализуете Dispose и null шаблоны в вашем коде. Если несколько потребителей имеют ссылку на один и тот же одноразовый объект, то этот объект Dispose, вероятно, будет вызываться несколько раз, поскольку эти потребители отбрасывают свои ссылки на него.

Ответ 3

Объекты должны быть толерантными к тому, что Dispose вызывается более одного раза, поскольку - особенно при наличии исключений - может быть сложно, чтобы код очистки знал наверняка, какие вещи были очищены, а какие нет. Отбрасывание полей IDisposable при их очистке (и толерантность к тем, которые уже имеют значение null) упростит удаление избыточных вызовов 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);
    }      
}