你如何防止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
有一个Driver
, Driver
有两个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
必须是IDisposable
。 Bus
创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))})); } } }