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

Gra*_*amS 137 .net c# garbage-collection dispose 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;
}
Run Code Online (Sandbox Code Playgroud)

A Bus有一个Driver,Driver有两个Shoe,每个Shoe有一个Shoelace.一切都很傻.

将一个IDisposable对象添加到Shoelace

后来我决定对它进行一些操作Shoelace可能是多线程的,所以我添加了一个EventWaitHandle用于与之通信的线程.所以Shoelace现在看起来像这样:

class Shoelace
{
    private AutoResetEvent waitHandle = new AutoResetEvent(false);
    bool tied = false;
    // ... other stuff ..
}
Run Code Online (Sandbox Code Playgroud)

在鞋带上实施IDisposable

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

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

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

或者(正如评论者所指出的那样)因为Shoelace它本身没有非托管资源,所以我可以使用更简单的dispose实现,而不需要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);
    }
}
Run Code Online (Sandbox Code Playgroud)

像IDisposable传播一样惊恐地观看

是的,那是固定的.但现在FxCop会抱怨Shoe创造一个Shoelace,所以也Shoe必须IDisposable如此.

Driver创造Shoe如此Driver必须IDisposable.而Bus创造Driver这样Bus必须IDisposable等等.

突然间,我的小改变Shoelace让我做了很多工作,我的老板想知道为什么我需要结帐Bus才能做出改变Shoelace.

问题

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

Grz*_*nio 34

你无法真正"阻止"IDisposable蔓延.有些类需要处理,AutoResetEvent并且最有效的方法是在Dispose()方法中执行它以避免终结器的开销.但是必须以某种方式调用此方法,因此在您的示例中,封装或包含IDisposable的类必须处理这些,因此它们也必须是一次性的,等等.避免它的唯一方法是:

  • 避免在可能的情况下使用IDisposable类,在单个位置锁定或等待事件,在单个位置保留昂贵的资源等
  • 只在你需要它们时才创建它们并在它之后处理它们(using模式)

在某些情况下,可以忽略IDisposable,因为它支持可选的大小写.例如,WaitHandle实现IDisposable以支持命名的Mutex.如果未使用名称,则Dispose方法不执行任何操作.MemoryStream是另一个例子,它不使用系统资源,它的Dispose实现也什么都不做.关于是否使用非托管资源的仔细思考可以是教学性的.因此,可以检查.net库的可用来源或使用反编译器.

  • 由于今天的实现没有做任何事情,你可以忽略它的建议是不好的,因为未来的版本可能实际上在Dispose中做了一些重要的事情,现在你很难找到泄漏. (4认同)

Jar*_*Par 19

在正确性方面,如果父对象创建并且基本上拥有现在必须是一次性的子对象,则无法阻止IDisposable通过对象关系传播.在这种情况下,FxCop是正确的,父级必须是IDisposable.

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

如果您可以通过实际使用WaitHandle的地图将WaitHandle移动到松散的关联中,那么您可以打破此链.

  • 在那里建立联系感觉很奇怪.这个AutoResetEvent对于Shoelace实现来说是私有的,所以在我看来公开公开它是错误的. (2认同)

Jor*_*dão 15

为了防止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();

    } 

  }
} 
Run Code Online (Sandbox Code Playgroud)

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

由于wait句柄是一个实现细节Shoelace,它不应该以任何方式改变它的公共接口,比如在声明中添加一个新接口.当你不再需要一次性场地时会发生什么,你会删除IDisposable声明吗?如果你考虑Shoelace 抽象,你很快就会意识到它不应该被基础设施依赖性污染,比如IDisposable.IDisposable应该为抽象封装了一个需要确定性清理的资源的类保留; 即,对于可处置性是抽象的一部分的类.

  • 对不起,我知道你可以通过一次性的,但我仍然没有看到它的工作.在我的例子中,`AutoResetEvent`用于在同一个类中运行的不同线程之间进行通信,因此它必须是成员变量.您不能将其范围限制为方法.(例如,想象一个线程只是通过阻塞`waitHandle.WaitOne()`来等待一些工作.然后主线程调用`shoelace.Tie()`方法,它只执行`waitHandle.Set()`和立即返回). (2认同)