假设我有一组简单的类:
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
。一切都很傻。
后来我决定Shoelace
上的某些操作可能是多线程的,所以我为要与之通信的线程添加EventWaitHandle
。所以Shoelace
现在看起来像这样:
class Shoelace
{
private AutoResetEvent waitHandle = new AutoResetEvent(false);
bool tied = false;
// ... other stuff ..
}
但现在Microsoft's FxCop会抱怨:“在'Shoelace'上实施IDisposable,因为它会创建以下IDisposable类型的成员:'EventWaitHandle'。”
好的,我在IDisposable
上实施了Shoelace
,我整洁的小班变成了这个可怕的混乱:
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)
和析构函数:
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);
}
}
是的,那是固定的。但现在FxCop会抱怨Shoe
创建了Shoelace
,因此Shoe
也必须IDisposable
。
Driver
创建Shoe
,因此Driver
必须为IDisposable
。
并Bus
创建Driver
,因此Bus
必须为IDisposable
,依此类推。
突然间,我对Shoelace
的小改变让我做了很多工作,而我的老板想知道为什么我需要结帐Bus
才能更改Shoelace
。
如何阻止IDisposable
的传播,但仍然确保您的非托管对象得到妥善处置?
答案 0 :(得分:34)
你无法真正“阻止”IDisposable蔓延。有些类需要处理,如AutoResetEvent
,最有效的方法是在Dispose()
方法中执行,以避免终结器的开销。但是必须以某种方式调用此方法,因此在您的示例中,封装或包含IDisposable的类必须处理这些,因此它们也必须是一次性的,等等。避免它的唯一方法是:
using
模式)在某些情况下,可以忽略IDisposable,因为它支持可选案例。例如,WaitHandle实现IDisposable以支持命名的Mutex。如果未使用名称,则Dispose方法不执行任何操作。 MemoryStream是另一个例子,它不使用系统资源,它的Dispose实现也什么都不做。关于是否使用非托管资源的仔细思考可以是教学性的。因此,可以检查.net库的可用来源或使用反编译器。
答案 1 :(得分:19)
就正确性而言,如果父对象创建并且基本上拥有现在必须是一次性的子对象,则无法阻止IDisposable通过对象关系传播。在这种情况下,FxCop是正确的,父级必须是IDisposable。
您可以做的是避免将IDisposable添加到对象层次结构中的叶类。这并不总是一件容易的事,但这是一项有趣的练习。从逻辑的角度来看,ShoeLace没有必要是一次性的。除了在这里添加WaitHandle之外,还可以在ShoeLace和WaitHandle之间添加一个关联点。最简单的方法是通过Dictionary实例。
如果你可以通过实际使用WaitHandle的地图将WaitHandle移动到松散关联中,那么你可以打破这个链。
答案 2 :(得分:14)
为了防止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
方法,并且该类不需要具有一次性字段,因此本身不需要是一次性的。
由于wait句柄是Shoelace
内部的实现细节,因此它不应以任何方式更改其公共接口,例如在其声明中添加新接口。当你不再需要一次性领域时会发生什么,你会删除IDisposable
声明吗?如果您考虑Shoelace
抽象,您很快就会意识到它不应该被基础结构依赖性污染,例如IDisposable
。 IDisposable
应该保留给那些抽象封装了一个需要确定性清理的资源的类;即,对于可处置性是抽象的一部分的类。
答案 3 :(得分:3)
这感觉很像是一个更高级别的设计问题,通常是“快速修复”陷入泥潭的情况。有关解决方法的更多讨论,您可能会发现this thread有帮助。
答案 4 :(得分: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
实例仍由Driver
创建,但仍需要以某种方式处理。
答案 5 :(得分:3)
如果您保持设计紧密耦合,我认为没有一种技术方法可以防止IDisposable蔓延。然后人们应该想知道设计是否正确。
在你的例子中,我认为让鞋子拥有鞋带是有意义的,也许,司机应该拥有他/她的鞋子。但是,总线不应该拥有驱动程序。通常情况下,公共汽车司机不会跟随公共汽车去scrapyard :)在司机和鞋子的情况下,司机很少自己制鞋,这意味着他们并不真正“拥有”它们。
另一种设计可能是:
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
{
...
}
不幸的是,新设计更加复杂,因为它需要额外的类来实例化和处理鞋子和驱动器的具体实例,但这种复杂性是解决问题所固有的。好处是公共汽车不再需要一次性处理,只是为了处理鞋带。
答案 6 :(得分:2)
这基本上是将Composition或Aggregation与Disposable类混合时会发生的情况。如上所述,第一个出路是将waitHandle重新制成鞋带。
话说回来,当你没有非托管资源时,可以大大删除Disposable模式。 (我仍然在寻找官方参考资料。)
但是你可以省略析构函数和GC.SuppressFinalize(this);并且可能会清理虚拟空隙Dispose(bool disposing)。
答案 7 :(得分: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))}));
}
}
}