你如何防止IDisposable传播给你所有的课程?

从这些简单的课程开始......

假设我有一组简单的类:

class Bus
{
    Driver busDriver = new Driver();
}

class Driver
{
    Shoe[] shoes = { new Shoe(), new Shoe() };
}

class Shoe
{
    Shoelace lace = new Shoelace();
}

class Shoelace
{
    bool tied = false;
}

Bus有一个DriverDriver有两个Shoe ,每个Shoe都有Shoelace 。 非常愚蠢。

向鞋带添加一个IDisposable对象

后来我决定Shoelace上的一些操作可能是多线程的,所以我添加了一个EventWaitHandle供线程进行通信。 所以Shoelace现在看起来像这样:

class Shoelace
{
    private AutoResetEvent waitHandle = new AutoResetEvent(false);
    bool tied = false;
    // ... other stuff ..
}

在鞋带上实现IDisposable

但现在微软的FxCop会抱怨说:“在'Shoelace'上实现IDisposable,因为它创建了以下IDisposable类型的成员:'EventWaitHandle'。”

好吧,我在Shoelace上实施了IDisposable ,我整洁的小班变成了这个可怕的混乱:

class Shoelace : IDisposable
{
    private AutoResetEvent waitHandle = new AutoResetEvent(false);
    bool tied = false;
    private bool disposed = false;

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

    ~Shoelace()
    {
        Dispose(false);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                if (waitHandle != null)
                {
                    waitHandle.Close();
                    waitHandle = null;
                }
            }
            // No unmanaged resources to release otherwise they'd go here.
        }
        disposed = true;
    }
}

或者(正如评论者所指出的那样),因为Shoelace本身没有非托管资源,所以我可以使用更简单的配置实现而不需要Dispose(bool)和Destructor:

class Shoelace : IDisposable
{
    private AutoResetEvent waitHandle = new AutoResetEvent(false);
    bool tied = false;

    public void Dispose()
    {
        if (waitHandle != null)
        {
            waitHandle.Close();
            waitHandle = null;
        }
        GC.SuppressFinalize(this);
    }
}

惊恐地看着IDisposable传播

这就是固定的。 但是现在FxCop会抱怨Shoe创造了Shoelace ,所以Shoe必须是IDisposable

并且Driver创建Shoe所以Driver必须是IDisposableBus创建Driver所以Bus必须是IDisposable等。

突然间,我对Shoelace小小改变引起了我很多工作,而我的老板不知道为什么我需要结账Bus来更换Shoelace

问题

你如何防止IDisposable这种传播,但仍然确保你的非托管对象妥善处置?


你不能真正“防止”IDisposable蔓延。 有些类需要处理,比如AutoResetEvent ,最有效的方法是在Dispose()方法中执行,以避免终结器的开销。 但是这个方法必须以某种方式被调用,就像在你的例子中那样,封装或包含IDisposable的类必须处理这些,所以它们也必须是一次性的,等等。避免它的唯一方法是:

  • 避免在可能的情况下使用IDisposable类,在单个地方锁定或等待事件,在一个地方保留昂贵的资源等
  • 只在需要时才创建它们,并在它们之后立即处理它们( using模式)
  • 在某些情况下,IDisposable可以被忽略,因为它支持可选的情况。 例如,WaitHandle实现IDisposable来支持一个已命名的Mutex。 如果名称未被使用,则Dispose方法不执行任何操作。 MemoryStream是另一个例子,它不使用系统资源,其Dispose实现也不做任何事情。 仔细考虑是否使用非托管资源可能是教学性的。 因此可以检查.net库的可用来源或使用反编译器。


    就正确性而言,如果父对象创建并实质上拥有一个现在必须是一次性的子对象,则无法通过对象关系阻止IDisposable的传播。 FxCop在这种情况下是正确的,父母必须是IDisposable。

    您可以做的是避免将IDisposable添加到对象层次结构中的叶类。 这并不总是一件容易的事,但这是一个有趣的练习。 从逻辑的角度来看,ShoeLace不需要是一次性的。 在这里添加一个WaitHandle,而不是在它使用的地方添加一个ShoeLace和一个WaitHandle之间的关联。 最简单的方法是通过一个Dictionary实例。

    如果你可以在实际使用WaitHandle的地方通过地图将WaitHandle移动到一个松散关联中,那么你可以打破这个链。


    为了防止IDisposable蔓延,你应该尝试在一个方法中封装一次性对象的使用。 尝试设计不同的Shoelace

    class Shoelace { 
      bool tied = false; 
    
      public void Tie() {
    
        using (var waitHandle = new AutoResetEvent(false)) {
    
          // you can even pass the disposable to other methods
          OtherMethod(waitHandle);
    
          // or hold it in a field (but FxCop will complain that your class is not disposable),
          // as long as you take control of its lifecycle
          _waitHandle = waitHandle;
          OtherMethodThatUsesTheWaitHandleFromTheField();
    
        } 
    
      }
    } 
    

    等待句柄的范围仅限于Tie方法,并且该类不需要有一次性字段,因此不需要本身是一次性的。

    由于等待手柄是Shoelace内部的实现细节,它不应该以任何方式改变它的公共接口,如在其声明中添加新的接口。 当你不再需要一次性字段时会发生什么,你会删除IDisposable声明吗? 如果您考虑Shoelace抽象,那么您很快就会意识到它不应该受到像IDisposable这样的基础设施依赖关系的污染。 IDisposable应该保留给那些抽象封装需要确定性清理的资源的类; 即对于可处置性是抽象的一部分的类。

    链接地址: http://www.djcxy.com/p/54499.html

    上一篇: How do you prevent IDisposable from spreading to all your classes?

    下一篇: What is the difference between using IDisposable vs a destructor in C#?