Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/24.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何更好地实现.NET IDisposable类?_C#_.net_Idisposable - Fatal编程技术网

C# 如何更好地实现.NET IDisposable类?

C# 如何更好地实现.NET IDisposable类?,c#,.net,idisposable,C#,.net,Idisposable,如果这个问题有点过于开放,请原谅我,但我在这里看到过类似的语言讨论帖子,所以我想我会冒险一试 无论如何,我已经阅读了一些MSDN帮助页面和各种其他博客,主题是正确实现IDisposable类。我觉得我对事物理解得很好,但我不得不怀疑在建议的类结构中是否存在缺陷: public class DisposableBase : IDisposable { private bool mDisposed; ~DisposableBase() { Dispose(f

如果这个问题有点过于开放,请原谅我,但我在这里看到过类似的语言讨论帖子,所以我想我会冒险一试

无论如何,我已经阅读了一些MSDN帮助页面和各种其他博客,主题是正确实现
IDisposable
类。我觉得我对事物理解得很好,但我不得不怀疑在建议的类结构中是否存在缺陷:

public class DisposableBase : IDisposable
{
    private bool mDisposed;

    ~DisposableBase()
    {
        Dispose(false);
    }

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

    protected virtual void Dispose(bool disposing)
    {
        if (!mDisposed)
        {
            if (disposing)
            {
                // Dispose managed resources
                mManagedObject.Dispose();
            }

            // Dispose unmanaged resources
            CloseHandle(mUnmanagedHandle);
            mUnmanagedHandle = IntPtr.Zero;

            mDisposed = true;
        }
    }
}
任何时候,只要将上述内容用作基类,就需要依赖子类的实现者在必要时正确重写Dispose(bool)方法。简言之,派生类必须确保从其重写版本中调用基Dispose(bool)方法。否则,基类的非托管资源可能永远不会被释放,从而破坏IDisposable接口的主要用途

我们都知道虚拟方法的好处,但在这种情况下,它们的设计似乎有缺陷。事实上,我认为当试图设计可视化组件和类似的基类/派生类结构时,虚拟方法的这一特殊缺点经常表现出来

考虑以下更改,使用受保护的事件而不是受保护的虚拟方法:

public class DisposeEventArgs : EventArgs
{
    public bool Disposing { get; protected set; }

    public DisposeEventArgs(bool disposing)
    {
        Disposing = disposing;
    }
}

public class DisposableBase : IDisposable
{
    private bool mDisposed;

    protected event EventHandler<DisposeEventArgs> Disposing;

    ~DisposableBase()
    {
        Dispose(false);
    }

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

    // This method is now private rather than protected virtual
    private void Dispose(bool disposing)
    {
        if (!mDisposed)
        {
            // Allow subclasses to react to disposing event
            AtDisposing(new DisposeEventArgs(disposing));

            if (disposing)
            {
                // Dispose managed resources
                mManagedObject.Dispose();
            }

            // Dispose unmanaged resources
            CloseHandle(mUnmanagedHandle);
            mUnmanagedHandle = IntPtr.Zero;

            mDisposed = true;
        }
    }

    private void AtDisposing(DisposeEventArgs args)
    {
        try
        {
            EventHandler<DisposeEventArgs> handler = Disposing;
            if (handler != null) handler(this, args);
        }
        catch
        {
        }
    }
}
公共类DisposeEventArgs:EventArgs
{
公共布尔处理{get;protected set;}
公共处置事件(bool处置)
{
处置=处置;
}
}
公共类DisposableBase:IDisposable
{
私人住宅;
受保护的事件处理程序;
~DisposableBase()
{
处置(虚假);
}
公共空间处置()
{
处置(真实);
总干事(本);
}
//此方法现在是私有的,而不是受保护的虚拟方法
私有无效处置(bool处置)
{
如果(!mDisposed)
{
//允许子类对事件作出反应
AtDisposing(新DispositeEventargs(disposing));
如果(处置)
{
//处置托管资源
mManagedObject.Dispose();
}
//处置非托管资源
闭合手柄(mUnmanagedHandle);
mUnmanagedHandle=IntPtr.Zero;
mDisposed=true;
}
}
处置时的私有无效(处置目标args)
{
尝试
{
EventHandler=Disposing;
if(handler!=null)handler(this,args);
}
抓住
{
}
}
}
在这种设计中,无论子类是否订阅Disposing事件,都将始终调用基类的Dispose(bool)方法。我在这个修改后的设置中看到的最大缺陷是,调用事件侦听器的时间没有预先确定的顺序。如果存在多个继承级别,这可能会有问题,例如子类的侦听器可能在其子类B的侦听器之前触发。这个缺陷严重到使我修改的设计无效吗


这种设计困境使我希望有一些类似于
virtual
的方法的修饰符,但这将确保始终调用基类的方法,即使子类重写了该函数。如果有更好的方法来实现这一点,我将非常感谢您的建议。

您在这里使用的是
事件
,而实际上您需要使用继承机制,如
虚拟
。对于这样的场景,我希望确保始终调用我的实现,但希望允许基类定制,我使用以下模式

private void Dispose(bool disposing)
  if (mDisposed) { 
    return;
  }

  if (disposing) {
    mManagedObject.Dispose();
  }

  // Dispose unmanaged resources
  CloseHandle(mUnmanagedHandle);
  mUnmanagedHandle = IntPtr.Zero;
  mDisposed = true;

  DisposeCore(disposing);
}

protected virtual void DisposeCore(bool disposing) {
  // Do nothing by default
}

通过这种模式,我确保了基类
Dispose
实现将始终被调用。派生类不能简单地忘记调用基方法来阻止我。他们仍然可以通过覆盖
DisposeCore
选择dispose模式,但不能破坏基类契约

派生类可以简单地重新实现
IDisposable
,从而防止调用dispose方法,因此您也无法确保这一点


就我个人而言,我不会使用这两种模式。我更喜欢基于
SafeHandle
和类似的机制,而不是自己实现终结器。

考虑清楚地表明Dispose没有被调用,以便有人能够捕获它。当然,只有在使用定义的调试编译器指令编译代码时,才会调用
Debug.WriteLine

public class DisposableBase : IDisposable
{
  private bool mDisposed;

  ~DisposableBase()
  {
      if (!mDisposed)
         System.Diagnostics.Debug.WriteLine ("Object not disposed: " + this + "(" + GetHashCode() + ")";
      Dispose(false);
  }

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

无论是否有子类重写Dispose()(可以是通过override或new),析构函数都将被调用,但您的析构函数将被调用(~DisposableBase()),因此我打赌,将您的清理逻辑放在这里可能是一个很好的起点

这是一篇关于析构函数的有趣文章:

您可以将其分解:

  • 只有非托管资源才需要析构函数(终结器)
  • 使用Safehandle可以将未管理的资源转换为托管资源
  • 因此,你不需要一个析构函数。这是处置模式的一半
参考设计使用
虚拟void Dispose(bool)
来解决基本/派生类问题。这就给派生类带来了调用
base.Dispose(disposing)
的负担,这是问题的核心。我使用两种方法:

1) 阻止它。有了一个密封的基类,你就不用担心了

sealed class Foo:IDisposable 
{ 
   void Dispose() { _member.Dispose(); } 
}
2) 检查一下。像@j-agent的答案,但有条件。当性能可能成为问题时,您不希望在生产代码中使用终结器:

class Foo:IDisposable 
{ 
  void Dispose() { Dispose(true); }

  [Conditional("TEST")]  // or "DEBUG"
  ~Foo { throw new InvalidOperation("somebody forgot to Dispose") } 
}

它可以工作,但是否值得偏离参考实现?@HenkHolterman-IMHO-yes。如果参考样本不能充分满足我的约束,我会根据需要偏离。我喜欢这个想法,而且它的代码肯定比使用事件少。但是,如果有一个以上的继承级别,那么这个继承级别是否仍然会受到相同的原始问题的影响(即孙辈不支持继承)