對象銷燬理解

當.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編程模式。

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