你如何防止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上的一些操作可能是multithreading的,所以我添加一个EventWaitHandle线程来进行通信。 所以Shoelace现在看起来像这样:

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

在鞋带上实施IDisposable

但现在微软的FxCop会抱怨说: “在鞋带上实现IDisposable,因为它创build了以下IDisposabletypes的成员:”EventWaitHandle“。

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

 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本身没有非托pipe资源,所以我可以使用更简单的configuration实现而不需要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创buildShoe所以Driver必须是IDisposableBus创buildDriver所以Bus必须是IDisposable等。

突然之间,我对Shoelace小小的改变,使我做了很多工作,老板不知道为什么我需要结账Bus来改变Shoelace

问题

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

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

  • 避免在可能的情况下使用IDisposable类,在单个地方locking或等待事件,在单个地方保存昂贵的资源等
  • 只有当你需要它们时才创build它们,并在它们之后( using模式)

在某些情况下,IDisposable可以被忽略,因为它支持可选的情况。 例如,WaitHandle实现了IDisposable来支持一个已命名的Mutex。 如果没有使用名称,则Dispose方法将不执行任何操作。 MemoryStream是另一个例子,它不使用系统资源,其Dispose实现也不做任何事情。 仔细考虑是否使用非托pipe资源可以是教学性的。 因此可以检查.net库的可用来源或使用反编译器。

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

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

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

为防止IDisposable蔓延,您应该尝试在一个方法中封装一次性对象的使用。 尝试devise不同的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应该保留给那些抽象封装需要确定性清理的资源的类; 即对于可处置性是抽象的一部分的类。

这就像一个更高层次的devise问题,就像“快速修复”陷入泥潭的情况一样。 有关出路的更多讨论,您可能会发现此线程有帮助。

有趣的是,如果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的一个限制,而不是一个解决scheme,因为在第一个版本中, Shoe实例仍然是由Driver创build的,仍然需要以某种方式处理。

如果你保持你的devise紧密结合,我不认为有一种技术方法可以防止IDisposable散布。 那么应该怀疑devise是否正确。

在你的例子中,我认为让鞋子拥有鞋带是有意义的,也许司机应该拥有他/她的鞋子。 但是,巴士不应该拥有司机。 通常情况下,巴士司机不会跟随公交车到scrapyard :)在司机和鞋的情况下,司机很less做自己的鞋,这意味着他们并不真正“拥有”他们。

另一种devise可以是:

 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 { ... } 

不幸的是,新的devise更复杂,因为它需要额外的类来实例化和处理鞋子和驱动程序的具体实例,但是这个复杂性是解决问题的固有问题。 好的是,公共汽车不再只是为了处理鞋带而一次性使用。

这基本上是将Composition或Aggregation与Disposable类混合时发生的情况。 如前所述,第一个出路是将waitHandle从鞋带中重构出来。

话虽如此,当您没有非托pipe资源时,可以大幅消除Disposable模式。 (我仍然在寻找这方面的官方参考。)

但是你可以省略析构函数和GC.SuppressFinalize(this); 也许可以清理虚拟空间Dispose(bool disposing)。

如何使用控制反转?

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