简体   繁体   English

您如何防止 IDisposable 传播到您的所有班级?

[英]How do you prevent IDisposable from spreading to all your classes?

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 Shoe s, each Shoe has a Shoelace . BusDriverDriver有两个Shoe ,每个Shoe有一个Shoelace All very silly.都非常傻。

Add an IDisposable object to Shoelace将 IDisposable object 添加到鞋带

Later I decide that some operation on the Shoelace could be multi-threaded, so I add an EventWaitHandle for the threads to communicate with.后来我决定Shoelace上的某些操作可以是多线程的,所以我添加了一个EventWaitHandle以便线程与之通信。 So Shoelace now looks like this:所以Shoelace现在看起来像这样:

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

Implement IDisposable on Shoelace在鞋带上实现 IDisposable

But now Microsoft's FxCop will complain: "Implement IDisposable on 'Shoelace' because it creates members of the following IDisposable types: 'EventWaitHandle'."但现在微软的 FxCop会抱怨: “在 'Shoelace' 上实现 IDisposable,因为它会创建以下 IDisposable 类型的成员:'EventWaitHandle'。”

Okay, I implement IDisposable on Shoelace and my neat little class becomes this horrible mess:好的,我在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;
    }
}

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

Watch in horror as IDisposable spreads惊恐地看着 IDisposable 传播

Right that's that fixed.没错,就是这么固定的。 But now FxCop will complain that Shoe creates a Shoelace , so Shoe must be IDisposable too.但是现在 FxCop 会抱怨Shoe创建了Shoelace ,所以Shoe也必须是IDisposable的。

And Driver creates Shoe so Driver must be IDisposable .并且Driver创建Shoe所以Driver必须是IDisposable And Bus creates Driver so Bus must be IDisposable and so on.并且Bus创建Driver所以Bus必须是IDisposable等等。

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 .突然间,我对Shoelace的小改动给我带来了很多工作,我的老板想知道为什么我需要结账Bus才能对Shoelace进行更改。

The Question问题

How do you prevent this spread of IDisposable , but still ensure that your unmanaged objects are properly disposed?您如何防止IDisposable的这种传播,但仍确保您的非托管对象得到正确处置?

You can't really "prevent" IDisposable from spreading.你不能真正“阻止” IDisposable 传播。 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.有些类需要被释放,比如AutoResetEvent ,最有效的方法是在Dispose()方法中进行,以避免终结器的开销。 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:但是这个方法必须以某种方式调用,所以就像在你的例子中一样,封装或包含 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)仅在需要时创建它们并在之后处理它们( using模式)

In some cases IDisposable can be ignored because it supports an optional case.在某些情况下,可以忽略 IDisposable,因为它支持可选情况。 For example, WaitHandle implements IDisposable to support a named Mutex.例如,WaitHandle 实现 IDisposable 以支持命名的 Mutex。 If a name is not being used, the Dispose method does nothing.如果未使用名称,则 Dispose 方法不执行任何操作。 MemoryStream is another example, it uses no system resources and its Dispose implementation also does nothing. MemoryStream 是另一个例子,它不使用系统资源,而且它的 Dispose 实现也不做任何事情。 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.因此可以检查 .net 库的可用源或使用反编译器。

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.就正确性而言,如果父 object 创建并基本上拥有一个子 object 现在必须是一次性的,则您无法通过 object 关系阻止 IDisposable 的传播。 FxCop is correct in this situation and the parent must be IDisposable. FxCop 在这种情况下是正确的,并且父级必须是 IDisposable。

What you can do is avoid adding an IDisposable to a leaf class in your object hierarchy.您可以做的是避免将 IDisposable 添加到 object 层次结构中的叶 class 中。 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.除了在此处添加 WaitHandle 之外,是否还可以在 ShoeLace 和 WaitHandle 的使用点之间添加关联。 The simplest way is through an Dictionary instance.最简单的方法是通过 Dictionary 实例。

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.如果您可以在实际使用 WaitHandle 时通过 map 将 WaitHandle 移动到松散关联中,那么您可以打破这个链。

To prevent IDisposable from spreading, you should try to encapsulate the use of a disposable object inside of a single method.为了防止IDisposable传播,您应该尝试将一次性 object 的使用封装在单个方法中。 Try to design Shoelace differently:尝试以不同的方式设计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();

    } 

  }
} 

The scope of the wait handle is limited to the Tie method, and the class doesn't need to have a disposable field, and so won't need to be disposable itself.等待句柄的 scope 仅限于Tie方法,而 class 不需要具有可丢弃字段,因此本身不需要可丢弃。

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.由于等待句柄是Shoelace内部的一个实现细节,它不应该以任何方式改变它的公共接口,比如在它的声明中添加一个新接口。 What will happen then when you don't need a disposable field anymore, will you remove the IDisposable declaration?当您不再需要一次性字段时会发生什么,您会删除IDisposable声明吗? If you think about the Shoelace abstraction , you quickly realize that it shouldn't be polluted by infrastructure dependencies, like IDisposable .如果您考虑Shoelace抽象,您很快就会意识到它不应该被基础设施依赖所污染,例如IDisposable IDisposable should be reserved for classes whose abstraction encapsulate a resource that calls for deterministic clean up; IDisposable应该保留给其抽象封装了需要确定性清理的资源的类; ie, for classes where disposability is part of the abstraction .即,对于可处置性是抽象一部分的类。

I don't think there is a technical way of preventing IDisposable from spreading if you keep your design so tightly coupled.如果您的设计保持如此紧密的耦合,我认为没有一种技术方法可以防止 IDisposable 传播。 One should then wonder if the design is right.然后人们应该怀疑设计是否正确。

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.好消息是,公交车不再需要仅仅为了处理鞋带而被丢弃。

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.有关出路的更多讨论,您可能会发现此线程很有帮助。

Interestingly if Driver is defined as above:有趣的是,如果Driver定义如上:

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 .然后当ShoeIDisposable时,FxCop (v1.36) 不会抱怨Driver也应该是IDisposable

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实例仍然由Driver创建,并且仍然需要以某种方式处理。

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.如前所述,第一个出路是用鞋带重构waitHandle。

Having said that, you can strip down the Disposable pattern considerably when you don't have unmanaged resources.话虽如此,当您没有非托管资源时,您可以大大减少 Disposable 模式。 (I'm still looking for an official reference for this.) (我仍在为此寻找官方参考。)

But you can omit the destructor and GC.SuppressFinalize(this);但是你可以省略析构函数和 GC.SuppressFinalize(this); and maybe cleanup the virtual void Dispose(bool disposing) a bit.并且可能稍微清理一下虚拟 void Dispose(bool disposing)。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 你如何命名你的ViewModel类? - How do you name your ViewModel classes? 如何找到实现IDisposable的所有类? - How to find all Classes implementing IDisposable? 你如何协调IDisposable和IoC? - How do you reconcile IDisposable and IoC? InternalsVisibleTo-如何防止某人创建具有相同名称的程序集并访问内部类或方法? - InternalsVisibleTo - How do you prevent someone from creating an assembly with same name and get access to internal classes or methods? 如何防止引用程序直接访问类? (。净) - How do you prevent referencing program from accessing classes directly? (.NET) 如何检测连接到无线网络的所有设备? - How do you detect all devices attached to your wireless network? 您将如何根据这些要求设计课程? - How will you design your classes with this requisites? 如何从ViewModel中分离ViewModel属性验证? - How do you decouple your ViewModel properties validation from ViewModel? 如何在代码中处理来自API /库的异常? - How do you handle exceptions from API/library in your code? 如何创建一个搜索栏,让您搜索数据库中的所有不同字段? - How do you create a search bar that will let you search for all the different fields in your database?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM