浅尝EffectiveC#_10

Item 17: Implement the Standard Dispose Pattern实现标准的释放模式

● 现在是时候来讨论如何写代码来管理这些类占用的非内存资源了。一个标准的模式就是利用.Net框架提供的方法处理非内存资源。Now it’s time to cover how to write your own resourcemanagement code when you create types that contain resources other than memory. A standard pattern is used throughout the .NET Framework for disposing of unmanaged resources.

● 在类继承关系中顶层的基类应该实现IDisposable接口来释放资源。这个类型也应该添加一个析构函数,做为最后的被动机制。The root base class in the class hierarchy should implement the IDisposable interface to free resources. This type should also add a finalizer as a defensive mechanism.这两个方法都应该是用虚方法来释放资源,这样可以让它的派生类重载这个函数来释放它们自己的资源。派生类只有在它自己须要释放资源时才重载这个函数,并且一定要记得调用基类的方法。

● 首先,如果你的类使用了非内存资源,则一定得有一个析构函数。你不能指望你的客户总是记得调用Dispose方法,否则当他们忘记时,你会丢失一些资源。这或许是因为他们没有调用Dispose的错误,但你也有责任。唯一可以确保非内存资源可以恰当释放的方法就是创建一个析构函数。所以,添加一个析构函数吧!

● 当GC运行时,它会直接从内存中移除不用析构的垃圾对象。而其它有析构函数的对象还保留在内存中。When the Garbage Collector runs, it immediately removes from memory any garbage objects that do not have finalizers.All objects that have finalizers remain in memory.这些对象被添加到一个析构队列中,GC会起动一个新的线程专门来析构这些对象。当析构线程完成它的工作后,这些垃圾对象就可以从内存中移除了。Objects that need finalization stay in memory for far longer than objects without a finalizer.就是说,需要析构的对象比不需要析构的对象在内存中待的时间要长。但你没得选择。如果你是采用的这种被动模式,当你的类型占用非托管资源时,你就必须写一个析构函数。但目前你还不用担心性能问题,下一步就保证你的用户使用更加简单,而且可以避免因为析构函数而造成的性能损失。

● 实现IDisposable接口是一个标准的模式来告诉用户和进行时系统:你的对象占有资源而且必须及时的释放。IDisposable接口只有一个方法:

public interface IDisposable
{
void Dispose( );
}

●实现IDisposable.Dispose()方法有责任完成下面的任务The implementation of your IDisposable.Dispose() method is responsible for four tasks::
1、释放所有的非托管资源。Freeing all unmanaged resources.
2、释放所有的托管资源(包括未挂起事件)。Freeing all managed resources (this includes unhooking events).
3、设置一个状态标记来标识对象已经被处理。你要在调用已经处理过的对象前,检验这个标记并且抛出一个ObjectDisposed的异常。Setting a state flag to indicate that the object has been disposed. You need to check this state and throw ObjectDisposed exceptions in your public methods, if any get called after disposing of an object.
4、阻止析构。你要调用GC.SuppressFinalize(this)来完成最后的工作。Suppressing finalization. You call GC.SuppressFinalize(this) to accomplish this task.

● 通过实现IDisposable接口,你完成了两件事:第一就是提供了一个机制来及时的释放所有占用的托管资源,You provide the mechanism for clients to release all managed resources that you hold in a timely fashion,另一个就是你提供了一个标准的模式让用户来释放非托管资源。you give clients a standard way to release all unmanaged resources.这是十分重要的,当你在你的类型上实现了IDisposable接口以后,用户就可以避免析构时的损失。

● 但是在你创建的机制中还是存在一些漏洞。如何让一个派生类清理自己的资源,难道还让基类处理?如果派生类重载了析构函数或者自己添加了对IDisposable的实现,这些方法都是要调用基方法的。否则,基类无法正确的清理资源。

● 同样,析构和处理共享了一些相同的职责:可以肯定析构方法和处理方法之间的代码有很多重复的。正如你会在原则26中学到的,重载接口的方法根本没有如你所期望的那样工作。Dispose标准模式中的第三个方法,通过一个受保护的辅助性虚函数,制造出它们的常规任务并且挂接到派生类来释放资源。基类包含接口的核心代码, 派生类提供的Dispose()虚函数或者析构函数来负责清理资源:

protected virtual void Dispose( bool isDisposing );

这个重载方法对finalize和Dispose,都是必须的,又因为它是虚函数,它为所有的派生类提供函数入口点。派生类可以重载这个函数,提供恰当的实现来释放它自己的资源,并且调用基类的函数。当isDisposing为true时你可能同时清理托管资源和非托管资源,当isDisposing为false时你只能清理非托管资源。两种情况下,都可以调用基类的Dispose(bool)方法让它去清理它自己的资源。

● 这里是一个简单的例子Here is a short sample that shows the framework of code you supply when you implement this pattern.MyResourceHog 类展示了IDisposable的实现,一个析构函数,并且创建了一个虚的Dispose方法:

public class MyResourceHog : IDisposable
{
// Flag for already disposed已经释放的标志
private bool alreadyDisposed = false;
// Implementation of IDisposable.对
// Call the virtual Dispose method.
// Suppress Finalization.
public void Dispose()
{
Dispose(
true);
GC.SuppressFinalize(
this);
}
// Virtual Dispose method
protected virtual void Dispose(bool isDisposing)
{
// Don't dispose more than once.
if (alreadyDisposed)
return;
if (isDisposing)
{
// elided: free managed resources here.
}
// elided: free unmanaged resources here.
// Set disposed flag:
alreadyDisposed = true;
}
public void ExampleMethod()
{
if (alreadyDisposed)
throw new ObjectDisposedException(
"MyResourceHog",
"Called Example Method on Disposed object");
// remainder elided.
}
}

如果派生类有另外的清理任务,就让它实现Dispose方法:

public class DerivedResourceHog : MyResourceHog
{
// Have its own disposed flag.
private bool disposed = false;
protected override void Dispose(bool isDisposing)
{
// Don't dispose more than once.
if (disposed)
return;
if (isDisposing)
{
// TODO: free managed resources here.
}
// TODO: free unmanaged resources here.
// Let the base class free its resources.
// Base class is responsible for calling
// GC.SuppressFinalize( )
base.Dispose(isDisposing);
// Set derived class disposed flag:
disposed = true;
}
}

注意,派生类和基类都有一个处理状态的标记,这完全是被动的。重复的标记掩盖了在处理时任何可能发生的错误,而且是单一的类型处理,而不是处理构成这个对象的所有类型。

你应该被动的写处理方法和析构函数write Dispose and finalize defensively,处理对象可能以任何顺序发生,你可能会遇到这种情况:你的类中某个成员在你调用Dispose方法以前已经被处理过了。你没有看到这种情况是因为Dispose()方法是可以多次调用的。如果在一个已经被处理过的对象上调用该方法,就什么也不发生。析构函数也有同样的规则。任何对象的引用存在于内存中时,你不用检测null引用。然而,你引用的对象可能已经处理掉了,或者它已经析构了。

●这就引入用了一个非常重要的忠告:对于任何与处理和资源清理相关的方法,你必须只释放资源! 不要在处理过程中添加其它任何的任务。对象在你创建它时出生,在垃圾回收器认领它时死亡。你可以认为当你的程序不能再访问它们时,它们是睡眠的。你无法访问对象,无法调用对象的方法。种种迹象表明,它们就像是死的。但对象在宣布死亡前,析构函数还有最后一气。析构函数什么也不应该做,就是清理非托管资源。如果析构函数通过某些方法让对象又变得可访问,那么它就复活了。(译注:析构函数不是用户调用的,也不由.Net系统调用,而是在由GC产生的额外线程上运行的) 它又活了,但这并不好。即使是它是从睡眼中唤醒的。这里有一个明显的例子:

public class BadClass
{
// Store a reference to a global object:
private static readonly List<BadClass> finalizedList =
new List<BadClass>();
private string msg;
public BadClass(string msg)
{
// cache the reference:
msg = (string)msg.Clone();
}
~BadClass()
{
// Add this object to the list.
// This object is reachable, no
// longer garbage. It's Back!
finalizedList.Add(this);
}
}

当一个BadClass对象的析构函数执行时,它把自己的一个引用添加到了全局的链表中。这使得它自己又是可达的,它就又活了。前面向你介绍的这个方法会遇到一些让人畏缩的难题。对象已经被析构了,所以垃圾回收器从此相信再也不用调用它的析构函数了。如果你实际要析构一个可达对象,这将不会成功。其次,你的一些资源可能不再有用。GC不再从内存上移除那些只被析构队列引用的对象,但它们可能已经析构了。如果是这样,它们很可能已经不能使用了。(译注:也就是说利用上面的那个方法让对象复活后,很有可能对象是不可用的。)尽管BadClass所拥有的成员还在内存里,它们像是可以被析构或者处理,但C#语言没有一个方法可以让你控制析构的次序,你不能让这样的结构可靠的运行。不要尝试。

我还没有看到这样的代码:用这样明显的方式来复活一个对象,除非是学术上的练习。但我看过这样的代码,析构函数试图完成一些实质的工作,最后还通过析构函数的调用把引用放到对象中,从而把自己复活。析构函数里面的代码看上去是精心设计的,另外还有处理函数里的。再检查一遍,这些代码是做了其它事情,而不是释放资源!这些行为会为你的应用程序在后期的运行中产生很多BUG。删除这些方法,确保析构函数和Dispose()方法除了清理资源外,什么也不做。

在托管环境里,你不用为每一个创建的类写析构函数;只有须要释放一些使用的非托管资源时才添加,或者你的类所包含的成员有实现了IDisposable接口的时候也要添加。即使如此,你也只用实现IDisposable接口完成所有的功能就行了,不用析构函数。否则,你会限制你的派生类实现实现标准的Dispose习惯。 遵守这个我所讲叙的标准的Dispose习惯。这会让你的程序生活变得轻松,也为你的用户,也为那些从你的类创建派生类的人。

原文地址:https://www.cnblogs.com/TivonStone/p/1757881.html