实现 Finalize 和 Dispose 以清理非托管资源
本资源整理自MSDN,希望自己加深对此标题与内容的理解.
实现 Finalize 和 Dispose 以清理非托管资源
类实例经常封装对不受运行库管理的资源(如窗口句柄 (HWND)、数据库连接等)的控制。因此,应该既提供显式方法也提供隐式方法来释放这些资源。通过在对象上实现受保护的Finalize(在 C# 和 C++ 中为析构函数语法)可提供隐式控制。当不再有任何有效的对象引用后,垃圾回收器在某个时间调用此方法。
在有些情况下,您可能想为使用该对象的程序员提供显式释放这些外部资源的能力,以便在垃圾回收器释放该对象之前释放这些资源。当外部资源稀少或者昂贵时,如果程序员在资源不再使用时显式释放它们,则可以获得更好的性能。若要提供显式控制,需实现IDisposable提供的 Dispose。在完成使用该对象的操作时,该对象的使用者应调用此方法。即使对对象的其他引用是活动的,也可以调用 Dispose。
注意,即使在通过Dispose 提供显式控制时,也应该使用 Finalize 方法提供隐式清理。Finalize 提供了候补手段,可防止在程序员未能调用 Dispose 时造成资源永久泄漏。
Finalize
下面的规则概括了 Finalize 方法的使用准则:
- 仅在要求终结的对象上实现 Finalize。存在与 Finalize 方法相关的性能开销。
- 如果需要 Finalize 方法,应考虑实现 IDisposable,以使类的用户可以避免因调用 Finalize 方法而带来的开销。
- 不要提高 Finalize 方法的可见性。该方法的可见性应该是 protected,而不是 public。
- 对象的 Finalize 方法应该释放该对象拥有的所有外部资源。此外,Finalize 方法应该仅释放由该对象控制的资源。Finalize 方法不应该引用任何其他对象。
- 不要对不是对象的基类的对象直接调用 Finalize 方法。在 C# 编程语言中,这不是有效的操作。
- 应在对象的 Finalize 方法中调用基类的 Finalize 方法。
下面的规则概括了 Dispose 方法的使用准则:
- 在封装明确需要释放的资源的类型上实现释放设计方案。用户可以通过调用公共 Dispose 方法释放外部资源。
- 在通常包含控制资源的派生类型的基类型上实现释放设计方案,即使基类型并不需要也如此。如果基类型有 Close 方法,这通常指示需要实现 Dispose。在这类情况下,不要在基类型上实现 Finalize 方法。应该在任何引入需要清理的资源的派生类型中实现 Finalize。
- 使用类型的 Dispose 方法释放该类型所拥有的所有可释放资源。
- 对实例调用了 Dispose 后,应通过调用 GC.SuppressFinalize 禁止 Finalize 方法运行。此规则的一个例外是当必须用 Finalize 完成 Dispose 没有完成的工作的情况,但这种情况很少见。
- 如果基类实现了 IDisposable,则应调用基类的 Dispose 方法。
- 不要假定 Dispose 将被调用。如果 Dispose 未被调用,也应该使用 Finalize 方法释放类型所拥有的非托管资源。
- 当资源已经释放时,在该类型上从实例方法(非 Dispose)引发一个 ObjectDisposedException。该规则不适用于 Dispose 方法,该方法应该可以在不引发异常的情况下被多次调用。
- 通过基类型的层次结构传播对 Dispose 的调用。Dispose 方法应释放由此对象以及此对象所拥有的任何对象所控制的所有资源。例如,可以创建一个类似 TextReader 的对象来控制 Stream 和 Encoding,两者均在用户不知道的情况下由 TextReader 创建。另外,Stream 和 Encoding 都可以获取外部资源。当对 TextReader 调用 Dispose 方法时,TextReader 应继而对 Stream 和 Encoding 调用 Dispose,使它们释放其外部资源。
- 考虑在调用了某对象的 Dispose 方法后禁止对该对象的使用。重新创建已释放的对象是难以实现的方案。
- 允许 Dispose 方法被调用多次而不引发异常。此方法在首次调用后应该什么也不做。
IDisposable 接口
此接口的主要用途是释放非托管资源。当不再使用托管对象时,垃圾回收器会自动释放分配给该对象的内存。但无法预测进行垃圾回收的时间。另外,垃圾回收器对窗口句柄或打开的文件和流等非托管资源一无所知。
将此接口的 Dispose 方法与垃圾回收器一起使用来显式释放非托管资源。当不再需要对象时,对象的使用者可以调用此方法。
using System;
using System.IO;
class Program
{
static void Main()
{
try
{
// Initialize a Stream resource to pass
// to the DisposableResource class.
Console.Write("Enter filename and its path: ");
string fileSpec = Console.ReadLine();
FileStream fs = File.OpenRead(fileSpec);
DisposableResource TestObj = new DisposableResource(fs);
// Use the resource.
TestObj.DoSomethingWithResource();
// Dispose the resource.
TestObj.Dispose();
}
catch (FileNotFoundException e)
{
Console.WriteLine(e.Message);
}
}
}
// This class shows how to use a disposable resource.
// The resource is first initialized and passed to
// the constructor, but it could also be
// initialized in the constructor.
// The lifetime of the resource does not
// exceed the lifetime of this instance.
// This type does not need a finalizer because it does not
// directly create a native resource like a file handle
// or memory in the unmanaged heap.
public class DisposableResource : IDisposable
{
private Stream _resource;
private bool _disposed;
// The stream passed to the constructor
// must be readable and not null.
public DisposableResource(Stream stream)
{
if (stream == null)
throw new ArgumentNullException("Stream in null.");
if (!stream.CanRead)
throw new ArgumentException("Stream must be readable.");
_resource = stream;
_disposed = false;
}
// Demonstrates using the resource.
// It must not be already disposed.
public void DoSomethingWithResource() {
if (_disposed)
throw new ObjectDisposedException("Resource was disposed.");
// Show the number of bytes.
int numBytes = (int) _resource.Length;
Console.WriteLine("Number of bytes: {0}", numBytes.ToString());
}
public void Dispose()
{
Dispose(true);
// Use SupressFinalize in case a subclass
// of this type implements a finalizer.
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
// If you need thread safety, use a lock around these
// operations, as well as in your methods that use the resource.
if (!_disposed)
{
if (disposing) {
if (_resource != null)
_resource.Dispose();
Console.WriteLine("Object disposed.");
}
// Indicate that the instance has been disposed.
_resource = null;
_disposed = true;
}
}
}