对象销毁理解

当.NET程序运行时,CLR负责监控所有应用程序所创建的所有对象,当发现对象不再使用时,就回销毁对象,并回收其资源。这就是CLR的垃圾收集机制,基于此,.NET程序员再不用管理内存分配与回收,极大地简化了.NET应用程序的开发。

但是有时我们也需要考虑对象销毁的问题,比如程序中我们使用了非托管资源,如文件句柄、用于线程同步的Mutex对象。这些资源应遵循“需要即创建用完即销毁”的原则。

与c++类似,c#程序员也可以为类定义一个析构函数,如下代码

class MyClass
{
    ~MyClass() { }
}

查看生成的IL代码如下:

可以看到,c#编译器为类生成了一个Finalize方法,查看Finalize代码,发现内部实现是一个try...catch结构,并在finally部分调用了基类的Finalize方法

Object的Finalize方法体是空的。

只有显示的编写了析构函数的类,c#编译器才会为其生成Finalize方法。当CLR的垃圾收集线程要回收一个定义了析构函数的对象时,它会自动调用Finalize方法。

我们知道,析构函数存在的主要目的释放非托管资源(比如文件句柄),因此这里只需要完成这一工作的代码(比如关闭文件句柄)。然而真这么做,有可能还会遇到问题,主要原因是因为对象的析构函数(即Finalize方法)被调用的时机是不可控的,它的调用由CLR垃圾收集机制负责,出于性能方面的考虑,CLR的垃圾收集线程只是在它认为合适的时机才运行,这样一来就有可能出现对象所占有的非托管资源迟迟不能得到释放的情况。最好有一种方法能够使程序员主动的以完全可控的方式去释放这些非托管资源,为此.NET提供了一个IDispose接口。

IDispose接口只定义了一个Dispose方法,任何一个希望手动回收非托管资源的类都应该实现此接口。

public interface IDisposable
{
    void Dispose();
}

通常我们在Dispose方法中编写代码释放非托管资源,在需要的时候可以随时调用显示的释放资源。

但是这里有一个问题,CLR的垃圾收集线程不会主动询问对象是否实现了IDisposable接口并自动调用它的Dispose方法。另外程序员很有可能在开发时忘记调用对象的Dispose方法,为了避免资源泄露,我们让对象的析构函数也调用Dispose方法。

class MyClass: IDisposable
{
    ~MyClass()
    {
        Dispose();
    }
    public void Dispose()
    {
    }
}

基于上述写法,我们得出一个结论,Dispose方法应该被允许调用多次而不能引发异常。

上面的做法会导致Dispose方法被调用多次,显然不太合理,如何处理该问题呢,.NET定义了一个希望程序员遵循的IDisposable编程模式,此模式的框架如下:

class MyClass : IDisposable
{
    ~MyClass()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        Dispose(true);  //将导致所有资源被释放
        GC.SuppressFinalize(this);  //  不需要再调用本对象的Finalize方法
    }
    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            //清理托管资源
        }
        //清理非托管资源
    }
}

首先我们给类MyClass添加了一个protected的虚方法Dispose(bool)。当参数为true时,可以编写一些必要的代码来清理托管资源,比如调用本对象所引用的其他对象的Dispose方法,不管参数值如何,都将清理非托管资源。

当用户显示调用IDisposable接口的Dispose()方法时,此方法传入true值调用上面的虚方法Dispose(bool),并通知CLR的垃圾收集线程不要再调用此对象的Finalize方法。

如果用户没有在程序中显示调用IDisposable接口的Dispose()方法,则由CLR的垃圾收集线程负责调用对象的Finalize方法完成资源清理工作,这时传入的参数为false,因为Finalize方法只负责释放非托管资源。

下面以FileStream类为例,看它是如何实现IDisposable编程模式的。

FileStream内部是操作文件的,因此它包含了一个文件句柄对象:

private Microsoft.Win32.SafeHandles.SafeFileHandle _handle;

FileStream的基类是Stream,它实现了IDisposable接口并应用了前面提到的IDisposable编程模式。看下它的析构函数

 

代码很简单,再看下FileStream类重写基类Dispose(bool)方法的实现代码:

protected override void Dispose(bool disposing)
{
    try
    {
        if (((this._handle != null) && !this._handle.IsClosed) && (this._writePos > 0))
        {
            this.FlushWrite(!disposing);
        }
    }
    finally
    {
        if ((this._handle != null) && !this._handle.IsClosed)
        {
            this._handle.Dispose(); //关闭非托管的文件句柄
        }
        this._canRead = false;
        this._canWrite = false;
        this._canSeek = false;
        base.Dispose(disposing);    //调用基类的Dispose(bool)方法
    }
}

可以看到不管参数如何,此方法完成了对非托管资源(即文件句柄)的释放工作,然后再调用基类Stream的Dispose(bool)方法,基类Stream的Dispose(bool)方法如下:

可以看到当参数为true时(即被显示调用)它会执行完成特定工作的代码,具体做什么我们不必关心,因为它封装到了FileStream类内部,外部不可控,以下是Stream类实现的IDisposable接口的Dispose方法,

public void Dispose()
{
    this.Close();
}
public virtual void Close()
{
    this.Dispose(true);
    GC.SuppressFinalize(this);
}

在基类库中凡是使用了非托管资源的类大都遵循了IDisposable编程模式。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章