你怎么防止IDisposable从扩展到所有你的课吗?
-
20-08-2019 - |
题
开始与这些简单类...
我是说我有一个简单的类是这样的:
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
s,每 Shoe
有一个 Shoelace
.所有非常愚蠢的。
增加一个IDisposable对象鞋带
后来我决定一些操作上的 Shoelace
可能是多线程,因此我添加一个 EventWaitHandle
对于该线通信。所以 Shoelace
现在看起来是这样的:
class Shoelace
{
private AutoResetEvent waitHandle = new AutoResetEvent(false);
bool tied = false;
// ... other stuff ..
}
实现IDisposable上鞋带
但是现在 微软的FxCop 会抱怨: "实现IDisposable上鞋带',因为它创建成员的以下IDisposable种类型:'安全访问'."
好吧,我实现的 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(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);
}
}
看恐怖,因为IDisposable传播
权利是固定的。但现在FxCop会抱怨, Shoe
创建一个 Shoelace
, ,所以 Shoe
必须 IDisposable
太。
和 Driver
创建 Shoe
所以 Driver
必须 IDisposable
.和 Bus
创建 Driver
所以 Bus
必须 IDisposable
等等。
突然我的小改变 Shoelace
引起了我很多的工作和我的老板是想知道为什么我要结帐 Bus
做出改变 Shoelace
.
这个问题
你怎么防止这种传播的 IDisposable
, 但仍然保证你不受管理的对象是妥善处置?
解决方案
你真的不能"防止"IDisposable扩散。有些课程需要设置,喜欢 AutoResetEvent
, 和最有效的方式是以这样做的 Dispose()
方法,以避免的开销终结器。但是这种方法必须以某种方式,所以,正是因为在您的例的类封或包含IDisposable必须处理这些,因此他们必须一次性使用,等等。只有这样,才能避免这是:
- 避免使用IDisposable类,如可能,锁或等待的事件,在单一的地方,保持昂贵资源的单位,等等
- 创造他们只有当你需要他们和处置他们只是之后(该
using
图案)
在某些情况下IDisposable可以忽略,因为它支持一个可选择的情况。例如,线程实现IDisposable支持一个名为互斥。如果一个名字没有被使用,处置的方法并没有什么。流是另一个例子,它使用没有系统资源及其处置的执行也什么都不做。谨慎思考是否不受管理的资源正被用于或不可以教学。因此可以审查可用资源。净库或使用反编译器.
其他提示
在准确性方面,你不能阻止IDisposable的传播通过对象的关系,如果父对象创建和本质拥有的子对象,现在必须是一次性的。 FxCop的是在这种情况下正确的并且父母必须IDisposable的。
你可以做的是避免在您的对象层次结构添加了IDisposable到叶类。这并不总是一件容易的事,但它是一个有趣的练习。从逻辑的角度来看,没有理由认为鞋带必须是一次性的。除了在此处添加的WaitHandle的,是不是也可以在它的使用点添加鞋带和WaitHandle的之间的关联。最简单的方法是通过Dictionary实例。
如果可以将WaitHandle的经由地图在WaitHandle的实际使用,那么您可以打破这种链。该点移动到一个松散的关联
要防止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
method,和类不需要有一个一次性字段,因此将不必是一次性的本身。
由于等待句柄是Shoelace
内部的实现细节,它不应该以任何方式改变它的公共接口,如在其声明中添加一个新的接口。当你不需要一次性场了,你会删除IDisposable
声明什么会怎样呢?如果你想想Shoelace
抽象,你很快意识到,它不应该是基础设施的依赖污染,像IDisposable
。 IDisposable
应保留类,它们的抽象封装,对于确定性的清理要求的资源;即,对于类,其中可处置是的一部分的抽象的
这感觉很像一个更高层次的设计问题,因为通常情况下,当一个“速战速决”转予陷入泥潭。对于如何进行更多的讨论时,你可能会发现这个线程有帮助的。
有趣的是,如果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
实例,但仍需要以某种方式设置。
我不认为这是防止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
{
...
}
在新的设计是不幸的是更为复杂,因为它需要额外的类实例化和鞋子和驱动程序的具体情况处置,而是要被解决的问题这种并发症是与生俱来的。好事是,公交车不再需要一次性只是为了鞋带处置的目的。
这基本上是当你混合组合或聚合一次性班会发生什么。如前所述,第一出路将是重构的WaitHandle了鞋带。
说了这么多,可以大大地剥离下来的一次性模式时,你不必非托管资源。 (我仍然在寻找用于此的官方参考。)
但是,可以省略析构函数和GC.SuppressFinalize(本);也许清理虚拟无效的Dispose(布尔处置)位。
如何使用控制反转?
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))}));
}
}
}