如何防止IDisposable避免扩散到所有课程?

时间:2022-09-25 10:28:31

Start with these simple classes...

Let's say I have a simple set of classes like this:

假设我有一组简单的类:

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

A Bus has a Driver, the Driver has two Shoes, each Shoe has a Shoelace. All very silly.

公共汽车有司机,司机有双鞋,每只鞋都有鞋带。都很愚蠢。

Add an IDisposable object to Shoelace

Later I decide that some operation on the Shoelace could be multi-threaded, so I add an EventWaitHandle for the threads to communicate with. So Shoelace now looks like this:

后来我决定鞋带上的一些操作可以是多线程的,所以我添加了一个EventWaitHandle,以便与线程通信。鞋带现在是这样的:

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

Implement IDisposable on Shoelace

But now Microsoft's FxCop will complain: "Implement IDisposable on 'Shoelace' because it creates members of the following IDisposable types: 'EventWaitHandle'."

但现在微软的FxCop将会抱怨:“在‘鞋带’上实现IDisposable,因为它会产生以下IDisposable类型:‘EventWaitHandle’。”

Okay, I implement IDisposable on Shoelace and my neat little class becomes this horrible mess:

好吧,我在鞋带上实现了IDisposable,而我那整洁的小类变成了糟糕的东西:

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

Or (as pointed out by commenters) since Shoelace itself has no unmanaged resources, I might use the simpler dispose implementation without needing the Dispose(bool) and Destructor:

或者(正如评论者指出的)由于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);
    }
}

Watch in horror as IDisposable spreads

Right that's that fixed. But now FxCop will complain that Shoe creates a Shoelace, so Shoe must be IDisposable too.

对的,是固定的。但是现在FxCop将会抱怨说鞋子会产生鞋带,所以鞋子一定也是一次性的。

And Driver creates Shoe so Driver must be IDisposable. And Bus creates Driver so Bus must be IDisposable and so on.

而司机创造鞋,所以司机必须是一次性的。公共汽车创造司机,所以公交车必须是一次性的等等。

Suddenly my small change to Shoelace is causing me a lot of work and my boss is wondering why I need to checkout Bus to make a change to Shoelace.

突然间,我对鞋带的小小改变给我带来了很多工作,我的老板也在想为什么我需要去收银台去换鞋带。

The Question

How do you prevent this spread of IDisposable, but still ensure that your unmanaged objects are properly disposed?

如何防止IDisposable的蔓延,但仍然确保未管理对象被正确地释放?

8 个解决方案

#1


34  

You can't really "prevent" IDisposable from spreading. Some classes need to be disposed, like AutoResetEvent, and the most efficient way is to do it in the Dispose() method to avoid the overhead of finalizers. But this method must be called somehow, so exactly as in your example the classes that encapsulate or contain IDisposable have to dispose these, so they have to be disposable as well, etc. The only way to avoid it is to:

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

  • avoid using IDisposable classes where possible, lock or wait for events in single places, keep expensive resources in single place, etc
  • 在可能的情况下避免使用IDisposable类,在单个地方锁定或等待事件,将昂贵的资源放在单个地方等等
  • create them only when you need them and dispose them just after (the using pattern)
  • 只在需要时才创建它们,然后在(使用模式)之后处理它们

In some cases IDisposable can be ignored because it supports an optional case. For example, WaitHandle implements IDisposable to support a named Mutex. If a name is not being used, the Dispose method does nothing. MemoryStream is another example, it uses no system resources and its Dispose implementation also does nothing. Careful thinking about whether an unmanaged resource is being used or not can be instructional. So can examining the available sources for the .net libraries or using a decompiler.

在某些情况下,IDisposable可以被忽略,因为它支持一个可选的案例。例如,WaitHandle实现IDisposable以支持命名互斥。如果一个名称没有被使用,Dispose方法什么也不做。MemoryStream是另一个例子,它不使用任何系统资源,它的Dispose实现也不做任何事情。仔细考虑是否使用了非托管资源,也可以是指导性的。也可以检查.net库或使用反编译器的可用源代码。

#2


19  

In terms of correctness, you can't prevent the spread of IDisposable through an object relationship if a parent object creates and essentially owns a child object which must now be disposable. FxCop is correct in this situation and the parent must be IDisposable.

在正确性方面,如果父对象创建并且本质上拥有一个必须是一次性的子对象,那么就不能阻止IDisposable通过对象关系的传播。FxCop在这种情况下是正确的,并且父节点必须是可丢弃的。

What you can do is avoid adding an IDisposable to a leaf class in your object hierarchy. This is not always an easy task but it's an interesting exercise. From a logical perspective, there is no reason that a ShoeLace needs to be disposable. Instead of adding a WaitHandle here, is it also possible to add an association between a ShoeLace and a WaitHandle at the point it's used. The simplest way is through an Dictionary instance.

您可以做的是避免向对象层次结构中的leaf类添加IDisposable。这并不总是一项容易的任务,但却是一项有趣的练习。从逻辑的角度来看,没有理由认为鞋带是一次性的。与其在这里添加一个WaitHandle,还可以在鞋带和WaitHandle之间添加一个关联。最简单的方法是通过字典实例。

If you can move the WaitHandle into a loose association via a map at the point the WaitHandle is actually used then you can break this chain.

如果你能通过地图将腰线移动到一个松散的关联中,那么你就可以打破这个链条。

#3


14  

To prevent IDisposable from spreading, you should try to encapsulate the use of a disposable object inside of a single method. Try to design Shoelace differently:

为了防止IDisposable避免蔓延,您应该尝试将一次性对象的使用封装在一个方法中。试着用不同的方式设计鞋带:

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

    } 

  }
} 

The scope of the wait handle is limited to the Tiemethod, and the class doesn't need to have a disposable field, and so won't need to be disposable itself.

等待句柄的范围仅限于Tiemethod,并且类不需要有一个可使用的字段,因此不需要自己可使用。

Since the wait handle is an implementation detail inside of the Shoelace, it shouldn't change in any way its public interface, like adding a new interface in its declaration. What will happen then when you don't need a disposable field anymore, will you remove the IDisposable declaration? If you think about the Shoelace abstraction, you quickly realize that it shouldn't be polluted by infrastructure dependencies, like IDisposable. IDisposable should be reserved for classes whose abstraction encapsulate a resource that calls for deterministic clean up; i.e., for classes where disposability is part of the abstraction.

由于等待句柄是鞋带内部的实现细节,所以它不应该以任何方式更改其公共接口,比如在声明中添加新的接口。当您不再需要一次性字段时,会发生什么情况?您会删除IDisposable declaration吗?如果您考虑鞋带抽象,您很快就会意识到它不应该被基础设施依赖所污染,就像IDisposable一样。应该为那些抽象封装了调用确定性清理的资源的类保留IDisposable;即。,用于可配置性是抽象的一部分的类。

#4


3  

This feels a lot like a higher-level design issue, as is often the case when a "quick fix" devolves into a quagmire. For more discussion of ways out, you might find this thread helpful.

这感觉很像一个高层次的设计问题,通常情况下,“快速修复”会变成一个泥潭。对于更多关于解决方法的讨论,您可能会发现这个线程很有帮助。

#5


3  

Interestingly if Driver is defined as above:

有趣的是,如果司机定义如下:

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

Then when Shoe is made IDisposable, FxCop (v1.36) does not complain that Driver should also be IDisposable.

那么,当鞋子被制成IDisposable时,FxCop (v1.36)并没有抱怨说,驾驶者也应该是一次性的。

However if it is defined like this:

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

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

then it will complain.

然后它会抱怨。

I suspect that this is just a limitation of FxCop, rather than a solution, because in the first version the Shoe instances are still being created by the Driver and still need to be disposed somehow.

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

#6


3  

I don't think there is a technical way of preventing IDisposable from spreading if you keep your design so tightly coupled. One should then wonder if the design is right.

如果你让你的设计紧密地结合在一起,我不认为有什么技术方法可以阻止IDisposable的使用。然后,人们应该怀疑这个设计是否正确。

In your example, I think it makes sense to have the shoe own the shoelace, and maybe, the driver should own his/her shoes. However, the bus should not own the driver. Typically, bus drivers do not follow buses to the scrapyard :) In the case of drivers and shoes, drivers seldom make their own shoes, meaning they don't really "own" them.

在你的例子中,我认为让鞋子拥有鞋带是合理的,也许,司机应该拥有鞋子。但是,公共汽车不应该拥有司机。一般情况下,公共汽车司机不会跟着公共汽车去垃圾场:)就司机和鞋子而言,司机很少自己做鞋子,这意味着他们并不是真正的“拥有”自己的鞋子。

An alternative design could be:

另一种设计可能是:

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

The new design is unfortunately more complicated, as it requires extra classes to instantiate and dispose of concrete instances of shoes and drivers, but this complication is inherent to the problem being solved. The good thing is that buses need no longer be disposable simply for the purpose of disposing of shoelaces.

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

#7


2  

This is basically what happens when you mix Composition or Aggregation with Disposable classes. As mentioned, the first way out would be to refactor the waitHandle out of shoelace.

当您将组合或聚合与一次性类混合时,基本上会发生这种情况。如前所述,第一个解决办法是将鞋带外面的腰重构。

Having said that, you can strip down the Disposable pattern considerably when you don't have unmanaged resources. (I'm still looking for an official reference for this.)

话虽如此,在没有非托管资源的情况下,您可以大大简化可弃模式。(我还在找官方的参考资料。)

But you can omit the destructor and GC.SuppressFinalize(this); and maybe cleanup the virtual void Dispose(bool disposing) a bit.

但是可以省略析构函数和GC.SuppressFinalize(this);或者清理虚拟空间(bool处理)。

#8


1  

How about using Inversion of Control?

如何使用反转控制?

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

#1


34  

You can't really "prevent" IDisposable from spreading. Some classes need to be disposed, like AutoResetEvent, and the most efficient way is to do it in the Dispose() method to avoid the overhead of finalizers. But this method must be called somehow, so exactly as in your example the classes that encapsulate or contain IDisposable have to dispose these, so they have to be disposable as well, etc. The only way to avoid it is to:

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

  • avoid using IDisposable classes where possible, lock or wait for events in single places, keep expensive resources in single place, etc
  • 在可能的情况下避免使用IDisposable类,在单个地方锁定或等待事件,将昂贵的资源放在单个地方等等
  • create them only when you need them and dispose them just after (the using pattern)
  • 只在需要时才创建它们,然后在(使用模式)之后处理它们

In some cases IDisposable can be ignored because it supports an optional case. For example, WaitHandle implements IDisposable to support a named Mutex. If a name is not being used, the Dispose method does nothing. MemoryStream is another example, it uses no system resources and its Dispose implementation also does nothing. Careful thinking about whether an unmanaged resource is being used or not can be instructional. So can examining the available sources for the .net libraries or using a decompiler.

在某些情况下,IDisposable可以被忽略,因为它支持一个可选的案例。例如,WaitHandle实现IDisposable以支持命名互斥。如果一个名称没有被使用,Dispose方法什么也不做。MemoryStream是另一个例子,它不使用任何系统资源,它的Dispose实现也不做任何事情。仔细考虑是否使用了非托管资源,也可以是指导性的。也可以检查.net库或使用反编译器的可用源代码。

#2


19  

In terms of correctness, you can't prevent the spread of IDisposable through an object relationship if a parent object creates and essentially owns a child object which must now be disposable. FxCop is correct in this situation and the parent must be IDisposable.

在正确性方面,如果父对象创建并且本质上拥有一个必须是一次性的子对象,那么就不能阻止IDisposable通过对象关系的传播。FxCop在这种情况下是正确的,并且父节点必须是可丢弃的。

What you can do is avoid adding an IDisposable to a leaf class in your object hierarchy. This is not always an easy task but it's an interesting exercise. From a logical perspective, there is no reason that a ShoeLace needs to be disposable. Instead of adding a WaitHandle here, is it also possible to add an association between a ShoeLace and a WaitHandle at the point it's used. The simplest way is through an Dictionary instance.

您可以做的是避免向对象层次结构中的leaf类添加IDisposable。这并不总是一项容易的任务,但却是一项有趣的练习。从逻辑的角度来看,没有理由认为鞋带是一次性的。与其在这里添加一个WaitHandle,还可以在鞋带和WaitHandle之间添加一个关联。最简单的方法是通过字典实例。

If you can move the WaitHandle into a loose association via a map at the point the WaitHandle is actually used then you can break this chain.

如果你能通过地图将腰线移动到一个松散的关联中,那么你就可以打破这个链条。

#3


14  

To prevent IDisposable from spreading, you should try to encapsulate the use of a disposable object inside of a single method. Try to design Shoelace differently:

为了防止IDisposable避免蔓延,您应该尝试将一次性对象的使用封装在一个方法中。试着用不同的方式设计鞋带:

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

    } 

  }
} 

The scope of the wait handle is limited to the Tiemethod, and the class doesn't need to have a disposable field, and so won't need to be disposable itself.

等待句柄的范围仅限于Tiemethod,并且类不需要有一个可使用的字段,因此不需要自己可使用。

Since the wait handle is an implementation detail inside of the Shoelace, it shouldn't change in any way its public interface, like adding a new interface in its declaration. What will happen then when you don't need a disposable field anymore, will you remove the IDisposable declaration? If you think about the Shoelace abstraction, you quickly realize that it shouldn't be polluted by infrastructure dependencies, like IDisposable. IDisposable should be reserved for classes whose abstraction encapsulate a resource that calls for deterministic clean up; i.e., for classes where disposability is part of the abstraction.

由于等待句柄是鞋带内部的实现细节,所以它不应该以任何方式更改其公共接口,比如在声明中添加新的接口。当您不再需要一次性字段时,会发生什么情况?您会删除IDisposable declaration吗?如果您考虑鞋带抽象,您很快就会意识到它不应该被基础设施依赖所污染,就像IDisposable一样。应该为那些抽象封装了调用确定性清理的资源的类保留IDisposable;即。,用于可配置性是抽象的一部分的类。

#4


3  

This feels a lot like a higher-level design issue, as is often the case when a "quick fix" devolves into a quagmire. For more discussion of ways out, you might find this thread helpful.

这感觉很像一个高层次的设计问题,通常情况下,“快速修复”会变成一个泥潭。对于更多关于解决方法的讨论,您可能会发现这个线程很有帮助。

#5


3  

Interestingly if Driver is defined as above:

有趣的是,如果司机定义如下:

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

Then when Shoe is made IDisposable, FxCop (v1.36) does not complain that Driver should also be IDisposable.

那么,当鞋子被制成IDisposable时,FxCop (v1.36)并没有抱怨说,驾驶者也应该是一次性的。

However if it is defined like this:

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

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

then it will complain.

然后它会抱怨。

I suspect that this is just a limitation of FxCop, rather than a solution, because in the first version the Shoe instances are still being created by the Driver and still need to be disposed somehow.

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

#6


3  

I don't think there is a technical way of preventing IDisposable from spreading if you keep your design so tightly coupled. One should then wonder if the design is right.

如果你让你的设计紧密地结合在一起,我不认为有什么技术方法可以阻止IDisposable的使用。然后,人们应该怀疑这个设计是否正确。

In your example, I think it makes sense to have the shoe own the shoelace, and maybe, the driver should own his/her shoes. However, the bus should not own the driver. Typically, bus drivers do not follow buses to the scrapyard :) In the case of drivers and shoes, drivers seldom make their own shoes, meaning they don't really "own" them.

在你的例子中,我认为让鞋子拥有鞋带是合理的,也许,司机应该拥有鞋子。但是,公共汽车不应该拥有司机。一般情况下,公共汽车司机不会跟着公共汽车去垃圾场:)就司机和鞋子而言,司机很少自己做鞋子,这意味着他们并不是真正的“拥有”自己的鞋子。

An alternative design could be:

另一种设计可能是:

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

The new design is unfortunately more complicated, as it requires extra classes to instantiate and dispose of concrete instances of shoes and drivers, but this complication is inherent to the problem being solved. The good thing is that buses need no longer be disposable simply for the purpose of disposing of shoelaces.

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

#7


2  

This is basically what happens when you mix Composition or Aggregation with Disposable classes. As mentioned, the first way out would be to refactor the waitHandle out of shoelace.

当您将组合或聚合与一次性类混合时,基本上会发生这种情况。如前所述,第一个解决办法是将鞋带外面的腰重构。

Having said that, you can strip down the Disposable pattern considerably when you don't have unmanaged resources. (I'm still looking for an official reference for this.)

话虽如此,在没有非托管资源的情况下,您可以大大简化可弃模式。(我还在找官方的参考资料。)

But you can omit the destructor and GC.SuppressFinalize(this); and maybe cleanup the virtual void Dispose(bool disposing) a bit.

但是可以省略析构函数和GC.SuppressFinalize(this);或者清理虚拟空间(bool处理)。

#8


1  

How about using Inversion of Control?

如何使用反转控制?

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