为什么IDisposable实现的设计方式如此

Chr*_*iso 12 .net c#

让我们来看看臭名昭着的IDisposable接口:

[ComVisible(true)]
public interface IDisposable
{
    void Dispose();
}
Run Code Online (Sandbox Code Playgroud)

和一个典型的实现,如MSDN所推荐的(我省略了检查当前对象是否已被处理):

public class Base : IDisposable
{
    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            // release managed
        }
        // release unmanaged
        disposed = true;
    }

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

    ~Base()
    {
        Dispose(false);
    }
}

public class Derived : Base
{
    protected override void Dispose(bool disposing)
    {
        base.Dispose(disposing);
        if (disposing)
        {
            // release managed
        }
        // release unmanaged
        disposed = true;
    }
}
Run Code Online (Sandbox Code Playgroud)

问题是:我认为这种实现是违反直觉的.它在基类和派生类中也有显着差异.派生类应该假设基类正确实现了IDisposable,然后重写Dispose(bool),它甚至不是原始接口的一部分.

我不得不承认,我想出了这个问题,因为我经常要求初级程序员在求职面试中实施IDisposable.如果他们不确切地知道应该怎么做,他们会想出一些接近这个的东西:

public class Base : IDisposable
{
    public virtual void Dispose()
    {
        // release managed and unmanaged
        GC.SuppressFinalize(this);
    }

    ~Base()
    {
        // release unmanaged
    }
}

public class Derived : Base
{
    public override void Dispose()
    {
        // release managed and unmanaged
        base.Dispose();
    }

    ~Derived()
    {
        // release unmanaged
    }
}
Run Code Online (Sandbox Code Playgroud)

对我而言,这种实施更加清晰,更加一致.当然,最糟糕的是我们必须在两个不同的地方释放非托管资源,但重要的是,可能超过99%的自定义类没有任何不受管理的东西,因此无论如何它们都不需要终结器.我无法向初级程序员解释为什么MSDN实现更好,因为我自己并不真正了解它.

所以我想知道,是什么导致了这种不同寻常的设计决策(使派生类覆盖与界面中的方法不同的方法,并让他考虑非常可能不包含的非托管资源).对此事有何想法?

小智 12

我通常会对派生类进行猜测.这是我的.snippet文件:

#region IDisposable pattern
/// <summary>
/// Dispose of (clean up and deallocate) resources used by this class.
/// </summary>
/// <param name="fromUser">
/// True if called directly or indirectly from user code.
/// False if called from the finalizer (i.e. from the class' destructor).
/// </param>
/// <remarks>
/// When called from user code, it is safe to clean up both managed and unmanaged objects.
/// When called from the finalizer, it is only safe to dispose of unmanaged objects.
/// This method should expect to be called multiple times without causing an exception.
/// </remarks>
protected virtual void Dispose(bool fromUser)
    {
    if (fromUser)   // Called from user code rather than the garbage collector
        {
        // Dispose of managed resources (only safe if called directly or indirectly from user code).
        try
            {
      DisposeManagedResources();
            GC.SuppressFinalize(this);  // No need for the Finalizer to do all this again.
            }
        catch (Exception ex)
            {
            //ToDo: Handle any exceptions, for example produce diagnostic trace output.
            //Diagnostics.TraceError("Error when disposing.");
            //Diagnostics.TraceError(ex);
            }
        finally
            {
            //ToDo: Call the base class' Dispose() method if one exists.
            //base.Dispose();
            }
        }
    DisposeUnmanagedResources();
    }
/// <summary>
/// Called when it is time to dispose of all managed resources
/// </summary>
  protected virtual void DisposeManagedResources(){}
/// <summary>
/// Called when it is time to dispose of all unmanaged resources
/// </summary>
  protected virtual void DisposeUnmanagedResources(){}
/// <summary>
/// Dispose of all resources (both managed and unmanaged) used by this class.
/// </summary>
public void Dispose()
    {
    // Call our private Dispose method, indicating that the call originated from user code.
    // Diagnostics.TraceInfo("Disposed by user code.");
    this.Dispose(true);
    }
/// <summary>
/// Destructor, called by the finalizer during garbage collection.
/// There is no guarantee that this method will be called. For example, if <see cref="Dispose"/> has already
/// been called in user code for this object, then finalization may have been suppressed.
/// </summary>
~$MyName$()
    {
    // Call our private Dispose method, indicating that the call originated from the finalizer.
    // Diagnostics.TraceInfo("Finalizer is disposing $MyName$ instance");
    this.Dispose(false);
    }
#endregion
Run Code Online (Sandbox Code Playgroud)


Ree*_*sey 8

所以我想知道,是什么导致了这种不同寻常的设计决策(使派生类覆盖与界面中的方法不同的方法,并让他考虑非常可能不包含的非托管资源).对此事有何想法?

主要问题是IDisposable是在框架已经设计并存在之后定义的.它旨在处理托管代码试图避免的情况 - 所以它实际上是一个边缘情况,如果是一个非常常见的情况.;)

顺便说一下,如果你看一下C++/CLI,就可以看出这一点.它是在IDisposable之后设计的,因此,以更自然的方式实现IDisposable(destructors [ ~ClassName]自动成为Dispose,终结器[ !ClassName]被视为终结器).

另一个问题是IDisposable处理多种情况.我编写了一个完整的博客系列,介绍了在包装本机代码时应该使用的不同实现,封装了实现IDisposable的类,并将其与因式类型一起使用.

从技术上讲,您只需要直接实现该接口.允许protected virtual void Dispose(bool disposing)方法的设计决策允许在公共接口中不容易且安全地处理的额外灵活性.