139

从这些简单的类开始......

假设我有一组简单的类,如下所示:

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;
}

ABus有 a Driver, theDriver有两个Shoes,每个Shoe都有 a Shoelace。都非常傻。

将 IDisposable 对象添加到 Shoelace

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

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

在鞋带上实现 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;
    }
}

或者(正如评论者所指出的)因为Shoelace它本身没有非托管资源,我可以使用更简单的 dispose 实现而不需要Dispose(bool)and 析构函数:

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如此。并创造必须如此等等。DriverIDisposableBusDriverBusIDisposable

突然间Shoelace,我BusShoelace.

问题

您如何防止IDisposable.

4

7 回答 7

36

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

  • 尽可能避免使用 IDisposable 类,在单个位置锁定或等待事件,将昂贵的资源保存在单个位置等
  • 仅在您需要它们时创建它们并在(using模式)之后处理它们

在某些情况下,可以忽略 IDisposable,因为它支持可选情况。例如,WaitHandle 实现 IDisposable 以支持命名的 Mutex。如果未使用名称,则 Dispose 方法不执行任何操作。MemoryStream 是另一个例子,它不使用系统资源,而且它的 Dispose 实现也不做任何事情。仔细考虑是否正在使用非托管资源可能具有指导意义。因此可以检查 .net 库的可用源或使用反编译器。

于 2009-03-19T11:21:54.270 回答
20

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

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

如果您可以在实际使用 WaitHandle 的点通过映射将 WaitHandle 移动到松散关联中,那么您可以断开此链。

于 2009-03-19T12:23:28.023 回答
16

为了防止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应该保留给其抽象封装了需要确定性清理的资源的类;即,对于可处置性是抽象一部分的类。

于 2010-06-09T12:48:27.343 回答
3

这基本上是当您将组合或聚合与一次性类混合时发生的情况。如前所述,第一个出路是用鞋带重构waitHandle。

话虽如此,当您没有非托管资源时,您可以大大减少 Disposable 模式。(我仍在为此寻找官方参考。)

但是你可以省略析构函数和 GC.SuppressFinalize(this); 并且可能稍微清理一下虚拟 void Dispose(bool disposing)。

于 2009-03-19T11:55:54.593 回答
3

有趣的是,如果Driver定义如上:

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

那么在Shoe制作时IDisposable,FxCop (v1.36) 不抱怨Driver应该也是IDisposable

但是,如果它是这样定义的:

class Driver
{
    Shoe leftShoe = new Shoe();
    Shoe rightShoe = new Shoe();
}

然后它会抱怨。

我怀疑这只是 FxCop 的一个限制,而不是一个解决方案,因为在第一个版本中,Shoe实例仍然由 the 创建,Driver并且仍然需要以某种方式处理。

于 2009-03-19T13:39:52.830 回答
3

如果您的设计保持如此紧密的耦合,我认为没有一种技术方法可以防止 IDisposable 传播。然后人们应该怀疑设计是否正确。

在您的示例中,我认为让鞋子拥有鞋带是有意义的,也许司机应该拥有他/她的鞋子。但是,公共汽车不应该拥有司机。通常,公共汽车司机不会跟随公共汽车去废品场:) 对于司机和鞋子,司机很少自己制作鞋子,这意味着他们并不真正“拥有”它们。

另一种设计可能是:

class Bus
{
   IDriver busDriver = null;
   public void SetDriver(IDriver d) { busDriver = d; }
}

class Driver : IDriver
{
   IShoePair shoes = null;
   public void PutShoesOn(IShoePair p) { shoes = p; }
}

class ShoePairWithDisposableLaces : IShoePair, IDisposable
{
   Shoelace lace = new Shoelace();
}

class Shoelace : IDisposable
{
   ...
}

不幸的是,新设计更加复杂,因为它需要额外的类来实例化和处理鞋子和驱动程序的具体实例,但这种复杂性是要解决的问题所固有的。好消息是,公交车不再需要仅仅为了处理鞋带而被丢弃。

于 2010-01-06T10:39:29.957 回答
1

如何使用控制反转?

class Bus
{
    private Driver busDriver;

    public Bus(Driver busDriver)
    {
        this.busDriver = busDriver;
    }
}

class Driver
{
    private Shoe[] shoes;

    public Driver(Shoe[] shoes)
    {
        this.shoes = shoes;
    }
}

class Shoe
{
    private Shoelace lace;

    public Shoe(Shoelace lace)
    {
        this.lace = lace;
    }
}

class Shoelace
{
    bool tied;
    private AutoResetEvent waitHandle;

    public Shoelace(bool tied, AutoResetEvent waitHandle)
    {
        this.tied = tied;
        this.waitHandle = waitHandle;
    }
}

class Program
{
    static void Main(string[] args)
    {
        using (var leftShoeWaitHandle = new AutoResetEvent(false))
        using (var rightShoeWaitHandle = new AutoResetEvent(false))
        {
            var bus = new Bus(new Driver(new[] {new Shoe(new Shoelace(false, leftShoeWaitHandle)),new Shoe(new Shoelace(false, rightShoeWaitHandle))}));
        }
    }
}
于 2014-07-02T11:34:55.960 回答