Effective C#之18: Implement the Standard Dispose Pattern

来源:互联网 发布:java安装失败 编辑:程序博客网 时间:2024/04/30 07:34
 

Item18: Implement the Standard Dispose Pattern

实现标准的Dispose模式

We've discussed the importance ofdisposing of objects that hold unmanaged resources. Now it's time to cover howto write your own resource-management code when you create types that containresources other than memory. A standard pattern is used throughout the .NETFramework for disposing of nonmemory resources. The users of your type willexpect you to follow this standard pattern. The standard dispose idiom freesyour unmanaged resources using the IDisposable interface when clients remember,and it uses the finalizer defensively when clients forget. It works with theGarbage Collector to ensure that your objects pay the performance penaltyassociated with finalizers only when necessary. This is the right way to handleunmanaged resources, so it pays to understand it thoroughly.

我们已经讨论过了释放拥有未托管资源的对象的重要性。当你创建包含有资源而不仅是内存的类型时,如何来写自己的资源管理代码,现在是时候来讲述了。一种释放非内存资源的标准模式在整个.Net框架下被使用,你的类型的用户希望你遵守该标准模式。标准的释放习惯是,当客户记得时,使用IDisposable接口来释放未托管的资源,当客户忘记时,使用终结器finalizer来进行防御。它和垃圾收集器一起工作,保证只有在必要的时候,你的对象才为使用终结器付出性能代价。这是处理未托管资源的正确方式,因此值得彻底的理解。

The root base class in the classhierarchy should implement the IDisposable interface to free resources. Thistype should also add a finalizer as a defensive mechanism. Both of theseroutines delegate the work of freeing resources to a virtual method thatderived classes can override for their own resource-management needs. Thederived classes need override the virtual method only when the derived classmust free its own resources and it must remember to call the base class versionof the function.

位于类体系根部的基类应该实现IDisposable接口来释放资源。该类型也应该添加一个终结器做为防御性的机制。这些程序都是将释放资源的工作委托给一个虚方法,派生类可以根据自己的资源管理需要重写该方法。只有当派生类必须释放它自己的资源并且必须记得调用基类的虚方法版本时,才需要重写该虚方法。

To begin, your class must have afinalizer if it uses nonmemory resources. You should not rely on clients to alwayscall the Dispose() method. You'll leak resources when they forget. It's theirfault for not calling Dispose, but you'll get the blame. The only way you canguarantee that nonmemory resources get freed properly is to create a finalizer.So create one.

首先,如果你的类使用非内存资源,它必须有一个终结器。你不能依赖于客户总是调用Dispose方法。如果他们忘记,就会发生资源泄漏。没有调用Dispose是他们的错误,但是你会被指责。能保障非内存资源被恰当释放的唯一方法就是创建终结器。因此,创建一个吧。

When the Garbage Collector runs,it immediately removes from memory any garbage objects that do not havefinalizers. All objects that have finalizers remain in memory. These objectsare added to a finalization queue, and the Garbage Collector spawns a newthread to run the finalizers on those objects. After the finalizer thread hasfinished its work, the garbage objects can be removed from memory. Objects thatneed finalization stay in memory for far longer than objects without afinalizer. But you have no choice. If you're going to be defensive, you mustwrite a finalizer when your type holds unmanaged resources. But don't worry aboutperformance just yet. The next steps ensure that it's easier for clients toavoid the performance penalty associated with finalization.

当垃圾收集器运行时,它会迅速从内存里移除任何没有终结器的垃圾对象。所有有终结器的对象都会留在内存里。这些对象被加入到终结队列中,垃圾收集器衍生新的线程来执行这些对象的终结器。在终结器线程结束工作后,垃圾对象就能从内存中移除了。需要终结的对象比没有终结器的对象在内存里会驻留更长时间。但是你没得选择。如果你打算做些防备,当你的类型有未托管资源时,必须编写一个终结器。但是现在还不必为性能担心。要避免和终结相关的性能损失,下一步会保证这对客户来说是简单的。

Implementing IDisposable is thestandard way to inform users and the runtime system that your objects holdresources that must be released in a timely manner. The IDisposable interfacecontains just one method:

要通知用户和运行时系统:你的对象占用的资源必须被以及时的释放,实现IDisposable是标准的方式。IDisposable接口仅包含一个方法:

  1.     public interface IDisposable
  2.     {
  3.         void Dispose();
  4.     }

The implementation of yourIDisposable.Dispose() method is responsible for four tasks:

你实现的IDisposable.Dispose()方法要对4个任务负责:

1.Freeing all unmanaged resources.

释放所有未托管资源

2.Freeing all managed resources(this includes unhooking events).

释放所有的托管资源(包括未挂钩的事件)

3.Setting a state flag to indicatethat the object has been disposed. You need to check this state and throwObjectDisposed exceptions in your public methods, if any get called afterdisposing of an object.

设置一个状态标志,指示该对象已经被释放。如果一个对象在释放后被调用,需要检查这个状态,并且在你的公共方法里抛出ObjectDisposed异常。

4.Suppressing finalization. Youcall GC.SuppressFinalize(this) to accomplish this task.

禁止终结。通过调用GC.SuppressFinalize(this)来完成这个任务。

You accomplish two things byimplementing IDisposable: You provide the mechanism for clients to release allmanaged resources that you hold in a timely fashion, and you give clients astandard way to release all unmanaged resources. That's quite an improvement.After you've implemented IDisposable in your type, clients can avoid thefinalization cost. Your class is a reasonably well-behaved member of the .NETcommunity.

通过实现IDisposable,完成2件事情:为客户提供及时释放所有占有的托管资源的机制;同时,为客户释放所有未托管资源提供了一个标准的方式。这是相当重要的改进。在实现了IDisposable之后,客户能避免终结器的代价。你的类就是.Net群体里具有良好行为的成员。

But there are still holes in themechanism you've created. How does a derived class clean up its resources andstill let a base class clean up as well? If derived classes override finalizeor add their own implementation of IDisposable, those methods must call thebase class; otherwise, the base class doesn't clean up properly. Also, finalizeand Dispose share some of the same responsibilities: You have almost certainlyduplicated code between the finalize method and the Dispose method. As you'lllearn in Item26, overriding interface functions does not work the way you'd expect. Thethird method in the standard Dispose pattern, a protected virtual helperfunction, factors out these common tasks and adds a hook for derived classes tofree resources they allocate. The base class contains the code for the coreinterface. The virtual function provides the hook for derived classes to cleanup resources in response to Dispose() or finalization:

但是在你已经创建的机制里面仍然存在漏洞。一个派生类如何清理自己的资源并且同时也让基类进行清理呢?如果派生类重写终结器或者添加它们自己对IDisposable的实现,那些方法必须调用基类,否则,基类就不能恰当的清理。同样,终结器和Dispose共享一些职责:在终结器方法和Dispose方法之间,你几乎有很多重复的代码。像你将在Item 26里面学到的一样,重写接口方法并不像你期望的那样工作。在标准Dispose模式中,第三个方法是protected辅助虚方法,提炼出这些通用的任务,为派生类添加一个钩子来释放它们分配的资源。基类包含了核心接口代码。虚方法为派生类提供了钩子来清理资源,对Dispose()或者终结器做出响应。

  1.    protected virtual void Dispose(bool isDisposing);

This overloaded method does thework necessary to support both finalize and Dispose, and because it is virtual,it provides an entry point for all derived classes. Derived classes canoverride this method, provide the proper implementation to clean up theirresources, and call the base class version. You clean up managed and unmanagedresources when isDisposing is True; clean up only unmanaged resources whenisDisposing is false. In both cases, call the base class's Dispose(bool) methodto let it clean up its own resources.

重载的方法做了必要的工作来同时支持终结器和Dispose,因为它是虚的,为所有的派生类提供了切入点。派生类能够重写该方法,提供合适的实现来清理它们的资源,同时调用基类的版本。当isDisposingTrue的话,清理托管和未托管的资源;当isDisposingfalse的时候,仅仅清理未托管资源。两种情况下,调用基类的Dispose(bool)方法来让它清理它自己的资源。

Here is a short sample that showsthe framework of code you supply when you implement this pattern. TheMyResourceHog class shows the code to implement IDisposable, a finalizer, andcreate the virtual Dispose method:

这里有一个简短的例子,展示了当你实现该模式时,要支持的代码的框架。MyResourceHog类展示了实现IDisposable、终结器、创建虚Dispose方法的代码

 

  1.    public class MyResourceHog : IDisposable
  2.     {
  3.         // Flag for already disposed
  4.         private Boolean alreadyDisposed = false;
  5.         // finalizer:
  6.         // Call the virtual Dispose method.
  7.         ~MyResourceHog()
  8.         {
  9.             Dispose(false);
  10.         }
  11.         // Implementation of IDisposable.
  12.         // Call the virtual Dispose method.
  13.         // Suppress Finalization.
  14.         public void Dispose()
  15.         {
  16.             Dispose(true);
  17.             GC.SuppressFinalize(true);
  18.         }
  19.         // Virtual Dispose method
  20.         protected virtual void Dispose(Boolean isDisposing)
  21.         {
  22.             // Don't dispose more than once.
  23.             if (alreadyDisposed)
  24.                 return;
  25.             if (isDisposing)
  26.             {
  27.                 // TODO: free managed resources here.
  28.             }
  29.             // TODO: free unmanaged resources here.
  30.             // Set disposed flag:
  31.             alreadyDisposed = true;
  32.         }
  33.    }

If a derived class needs toperform additional cleanup, it implements the protected Dispose method:

如果一个派生类需要执行额外的清理,它就实现protected Dispose方法:

  1.   public class DerivedResourceHog : MyResourceHog
  2.     {
  3.         // Have its own disposed flag.
  4.         private Boolean disposed = false;
  5.         protected override void Dispose(Boolean isDisposing)
  6.         {
  7.             // Don't dispose more than once.
  8.             if (disposed)
  9.                 return;
  10.             if (isDisposing)
  11.             {
  12.                 // TODO: free managed resources here.
  13.             }
  14.             // TODO: free unmanaged resources here.
  15.             // Let the base class free its resources.
  16.             // Base class is responsible for calling
  17.             // GC.SuppressFinalize( )
  18.             base.Dispose(isDisposing);
  19.             // Set derived class disposed flag:
  20.             disposed = true;
  21.         }
  22.      }

Notice that both the base classand the derived class contain a flag for the disposed state of the object. Thisis purely defensive. Duplicating the flag encapsulates any possible mistakesmade while disposing of an object to only the one type, not all types that makeup an object.

注意,基类和派生类都包含了对象的“释放”状态标志,这完全是防卫性的。复制该标志将在释放一个对象时任何可能的错误封装在仅仅这一个类型里,而不是所有使用该对象的类型。

You need to write Dispose andfinalize defensively. Disposing of objects can happen in any order. You willencounter cases in which one of the member objects in your type is alreadydisposed of before your Dispose() method gets called. You should not view thatas a problem because the Dispose() method can be called multiple times. If it'scalled on an object that has already been disposed of, it does nothing.Finalizers have similar rules. Any object that you reference is still inmemory, so you don't need to check null references. However, any object thatyou reference might be disposed of. It might also have already been finalized.

你需要防卫性的编写Disposefinalize。对对象的释放可能以任何顺序发生。你可能遭遇这种情况:在Dispose()方法被调用前,类型中的一个成员对象已经被释放了。你不应该将这看做是问题,因为Dispose()方法会被调用多次。如果它在一个已经被释放的对象上被调用,什么也不做。Finalizer有同样的规则:你引用的任何对象依然在内存里,因此你没必要去检查空引用。然而,你引用的任何对象,可能已经被释放了,也可能已经被终结了。

This brings me to the mostimportant recommendation for any method associated with disposal or cleanup:You should be releasing resources only. Do not perform any other processingduring a dispose method. You can introduce serious complications to objectlifetimes by performing other processing in your Dispose or finalize methods.Objects are born when you construct them, and they die when the GarbageCollector reclaims them. You can consider them comatose when your program canno longer access them. If you can't reach an object, you can't call any of itsmethods. For all intents and purposes, it is dead. But objects that havefinalizers get to breathe a last breath before they are declared dead.Finalizers should do nothing but clean up unmanaged resources. If a finalizersomehow makes an object reachable again, it has been resurrected. It's aliveand not well, even though it has awoken from a comatose state. Here's anobvious example:

对任何伴有disposal或者清理工作的方法,最重要的建议是:你应该仅仅释放资源,在dispose方法期间,不要执行任何其它处理。在你的Dispose或者finalizer方法里执行其它处理,会给对象的生命周期引入严重的复杂性。当你构建对象时,对象就诞生了,当GC收回它们的时候,它们就死亡了。当你的程序不能再访问它们的时候,你可以认为它们是休眠的。如果你不能到达一个对象,就不能调用它们的任何方法。出于所有的愿望和目的,它已经死了。但是有finalizer的对象,在它们被宣布死亡之前,会呼吸最后一次。Finalizer除了清理未托管资源之外,不应该做任何事情。如果一个终结器不知何故的让一个对象又能被到达了,它就复活了。它活过来了,但是并不好,即使它是从休眠状态醒过来也不好。这是一个明显的例子:

  1.    public class BadClass
  2.     {
  3.         // Store a reference to a global object:
  4.         private readonly ArrayList finalizedList;
  5.         private string msg;
  6.         public BadClass(ArrayList badList, string msg)
  7.         {
  8.             // cache the reference:
  9.             finalizedList = badList;
  10.             this.msg = (string)msg.Clone();
  11.         }
  12.         ~BadClass()
  13.         {
  14.             // Add this object to the list.
  15.             // This object is reachable, no
  16.             // longer garbage. It's Back!
  17.             finalizedList.Add(this);
  18.         }
  19.    }

When a BadClass object executesits finalizer, it puts a reference to itself on a global list. It has just madeitself reachable. It's alive again! The number of problems you've justintroduced will make anyone cringe. The object has been finalized, so theGarbage Collector now believes there is no need to call its finalizer again. Ifyou actually need to finalize a resurrected object, it won't happen. Second,some of your resources might not be available. The GC will not remove frommemory any objects that are reachable only by objects in the finalizer queue,but it might have already finalized them. If so, they are almost certainly nolonger usable. Although the members that BadClass owns are still in memory,they will have likely been disposed of or finalized. There is no way in thelanguage that you can control the order of finalization. You cannot make thiskind of construct work reliably. Don't try.

当一个BadClass对象执行它的终结器时,它将一个对自己的引用放入一个全局列表里面。它仅仅是让自己能够可达。它又活了。你刚刚引入的问题数量会让任何人畏缩。对象已经被终结了,因此垃圾收集器现在相信没有必要再次调用它的终结器了。如果你实际上需要终结一个复活的对象,这不会发生。第二,你的一些资源可能是无效的。GC将不会从内存里面移除任何仅仅由终结器队列可到达的对象,但是可能已经终结了它们。如果那样的话,它们几乎是不再可用的。虽然BadClass的成员仍然在内存里,它们将会很可能被释放或者终结。在语言里,没有办法控制终结的次序。你不能使这种结构可靠。不用担心。

I've never seen code that hasresurrected objects in such an obvious fashion, except as an academic exercise.But I have seen code in which the finalizer attempts to do some real work andends up bringing itself back to life when some function that the finalizercalls saves a reference to the object. The moral is to look very carefully atany code in a finalizer and, by extension, both Dispose methods. If that codeis doing anything other than releasing resources, look again. Those actionslikely will cause bugs in your program in the future. Remove those actions, andmake sure that finalizers and Dispose() methods release resources and donothing else.

除了作为学术性练习外,我从来没有见过这种代码:这么明显的存在复活的对象。但是我见过这样的代码:终结器试图做一些实际的工作,当终结器调用的一些方法保存了对对象的引用是,最后将自己复活。有良心的做法是仔细查看在终结器里面的任何代码,最好 ,扩大到Dispose方法里。如果那些代码不止做了释放资源的事情,就要再次检查了。那些动作将来很可能在你的程序里面引起bug。移除那些动作,确保终结器和Dispose()方法除了释放资源外什么也不做。

In a managed environment, you donot need to write a finalizer for every type you create; you do it only fortypes that store unmanaged types or when your type contains members thatimplement IDisposable. Even if you need only the Disposable interface, not afinalizer, implement the entire pattern. Otherwise, you limit your derivedclasses by complicating their implementation of the standard Dispose idiom.Follow the standard Dispose idiom I've described. That will make life easier foryou, for the users of your class, and for those who create derived classes fromyour types.

在一个托管的环境里,没必要为每个自己创建的类型都提供终结器,只需要为存储未托管类型的类型或者含有实现了IDisposable成员的类型这么做。甚至如果你仅仅需要Dispose接口,而不是一个终结器,请实现整个模式。否则,你限制了你的派生类,使它们在实现标准的Dispose习惯上变得复杂。遵循我已经描述的标准的Dispose习惯。那会使你、你的类的用户、从你的类型创建派生类的人的生活更容易。