IDisposable接口的理解

 

本人最近接觸一個項目,在這個項目裏面看到很多類實現了IDisposable接口 .在我以前的項目中都很少用過這個接口,只知道它是用來手動釋放資源 的.這麼多地方用應該有它的好處,爲此自己想對它有進一步的瞭解,但這個過程遠沒有我想象中的簡單.

    IDisposable接口定義:定義一種釋放分配的資源的方法。

    .NET 平臺在內存管理方面提供了GC(Garbage Collection),負責自動釋放託管資源和內存回收的工作,但它無法對非託管資源進行釋放 ,這時我們必須自己提供方法來釋放對象內分配的非託管資源,比如你在對象的實現代碼中使用了一個COM對象 最簡單的辦法可以通過實現Finalize ()來釋放非託管資源,因爲GC在釋放對象時會檢查該對象是否實現了 Finalize() 方法。 有一種更好的,那就是通過實現一個接口顯式的提供給客戶調用端手工釋放對象的方法,而不是傻傻的等着GC來釋放我們的對象.這種實現並不一定要使用了非託管資源後才用,如果你設計的類會在運行時有非常大的實例(象 GIS 中的Geometry),爲了優化程序性能,你也可以通過實現該接口讓客戶調用端在確認不需要這些對象時手工釋放它們 .

    在定義一個類時,可以使用兩種機制來自動釋放未託管的資源.這些機制通常放在一起實現.因爲每個機制都爲問題提供了略爲不同的解決方法.這兩種機制是:
     第一:聲明一個析構函數,作爲類的一個成員.在GC回收資源時會調用.
     第二:在類中實現IDisposable接口     

    本人對IDisposable接口的理解是這樣的:

    這種手動釋放資源的方式肯定要比等待GC來回收要效率高啊,於是出現了下面的示例類代碼:
    
    這個Foo類實現了IDisposable接口,裏面有一個簡單的方法:增加一個用戶.


   析構函數的問題:
      執行的不確定性:析構函數是由GC調用的,而GC的調用是不確定的.如果對象佔用了比較重要的資源,應儘可以早的釋放資源.

   IDisposable接口定義了一個模式 ,爲釋未託管資源提供了確定的機制,並避免產生析構函數固有的與GC相關的問題.

   在實際應用了,常常是結合兩種方法來取長補短.之所以要加上析構函數,是防止客戶端沒有調用Dispose方法.


  public   class  Foo : IDisposable
    
{
        
///   <summary>
        
///  實現IDisposable接口
        
///   </summary>

         public   void  Dispose()
        
{
            Dispose(
true );
            
// .NET Framework 類庫
            
//  GC..::.SuppressFinalize 方法 
            
// 請求系統不要調用指定對象的終結器。
            GC.SuppressFinalize( this );
        }

        
///   <summary>
        
///  虛方法,可供子類重寫
        
///   </summary>
        
///   <param name="disposing"></param>

         protected   virtual   void  Dispose( bool  disposing)
        
{
            
if  ( ! m_disposed)
            
{
                
if  (disposing)
                
{
                    
//  Release managed resources
                }


                
//  Release unmanaged resources

                m_disposed 
=   true ;
            }

        }

        
///   <summary>
        
///  析構函數
        
///  當客戶端沒有顯示調用Dispose()時由GC完成資源回收功能
        
///   </summary>

         ~ Foo()
        
{
            Dispose(
false );
        }

        
///   <summary>
        
///  增加一個用戶
        
///   </summary>

         public   bool   AddUser()
        
{
            
// 代碼省略
             return   true ;
        
        }

        
///   <summary>
        
///  是否已經被釋放過,默認是false
        
///   </summary>

         public    bool  m_disposed;
        
// private IntPtr handle; 

    }



      客戶端是這樣調用的:先實例化對象,然後增加一個用戶,此時銷燬對象.

      

Foo _foo  =   null ;
            _foo 
=   new  Foo();
            
// 資源是否已經被釋放
            
// 第一次默認爲false;
             bool  isRelease3  =  _foo.m_disposed;
            
// 增加用戶
             bool  isAdded =  _foo.AddUser();
            
// 不再用了,釋放資源
            _foo.Dispose();

 

   

     C#編程的一個優點是程序員不需要擔心具體的內存管理,尤其是垃圾收集器會處理所有的內存清理 工作。用戶可以得到像C++語言那樣的效率,而不需要考慮像在C++中那樣內存管理工作的複雜性。雖然不必 手工管理內存,但如果要編寫高效的代碼,就仍需理解後臺發生的事情。

    一面運行沒有錯誤,可總想知道這個dispose方法到底做了些什麼.既然是釋放資源,那麼類被釋放後應該就被銷燬,它的引用應該是不存在的,於是本人的測試代碼如下:

   

try
            
{
                
if  (_foo  ==   null )
                
{
                    
// 對象調用Dispose()後應該運行到此外
                    Response.Write( " 資源已經釋放啦! " );

                }

                
else
                
{
                    Response.Write(_foo.GetType().ToString());
                    
// 資源是否已經被釋放 此時爲true
                     bool  isRelease4  =  _foo.m_disposed;
                    
bool  isAdded2  =  _foo.AddUser();

                }


            }

            
catch  (Exception ex)
            
{
                Response.Write(
" ERR " );

            }


     本想應該會運行Response.Write("資源已經釋放啦!"), 可是結果相反,它的引用依然存在.這讓我不解,後來得到園友jyk的指點,他讓我試下,.net下實現了dispose方法的類,我就用Stream試了下,測試結果好下:

    


 Stream _s  =   this .FileUpload1.PostedFile.InputStream;
            
// 客戶端文件大小 爲了判斷對象是否被銷燬
             long  orgLength  =  _s.Length;
            _s.Dispose();
            
try
            
{
                
if  (_s  ==   null )
                
{
                    Response.Write(
" 資源已經釋放啦! " );

                }

                
else
                
{
                    Response.Write(_s.GetType().ToString());
                    
// 客戶端文件大小 此處爲釋放資源後
                    
// 運行結果表明,此時的文件流的大小是0
                    
// 說明資源已經成功釋放
                     long  _length =  _s.Length;

                }


            }

            
catch  (Exception ex)
            
{
                Response.Write(
" ERR " );

            }

     

      運行結果我們可以非常清楚的看出,Stream資源已經被釋放,因爲兩次訪問Stream的大小,發現在dispose後的大小爲零.這就好像是第一次初始化的結果.但Stream屬於非託管資源,如果是託管資源呢? 在Foo的測試代碼中發現,釋放前後的變量 (m_disposed ,調用Dispose前爲false,調用後爲true ,而且還可以調用類的方法 )發生了變化 ,並不是我想象當中的初始化.這是讓我一直不解的地方.

     後來在資料書上看,發現IDisposable接口是專門針對未託管資源而設計的.它在託管資源上沒有特別大的幫助 .最終的資源回收工作還得要GC .我們看下託管資源和非託管資源在內存上的分配情況.

    /*非託管資源一般都是放在堆棧上,而託管資源都是存儲在堆上.*/  非常感謝 Angel Lucifer 的指教,本人見笑了 特此刪除 :
     

     
值類型與引用類型在內存分配上的分別:

     值類型存儲在堆棧中 ,堆棧的工作原理就是先進後出.它在釋放資源的順序上與定義變量時分配內存的順序相反.值變量一旦出了作用域就會從堆棧中刪除對象.
 
    引用類型則存儲在堆中 .,當new一個類時,此時就會爲對象分配內存存入托管堆中,它可以在方法退出很長的時間後仍然可以使用.我以一句常用的實例類的語句來說明下.
     
    classA a=new  classA();
 
    這句非常平常的語句其實可以分成兩部分來看:

    第一:classA a;聲明一個classA的引用a,在堆棧上給這個引用分配存儲空間.它只是個引用,並不是真正的對象.它包含存儲對象的地址.
    第二:a=new classA();分配堆上的內存,以存儲真正的對象.然後修改a的值爲新對象的內存地址.

    當引用出了作用域後,就會從堆棧上刪除引用,但引用對象的數據仍然存儲在託管堆中,一直到程序停止,或者是GC刪除.

    所在這點就可以解釋我上面寫的Foo類在調用Dispose方法後,程序仍然可以訪問對象的原因了.

    /*我認爲堆是否就有像asp.net中的緩存功能,它可以將對象緩存起來,對象只要創建一次就可以在一定的有限時間內存在.*/
    非常感謝 Angel Lucifer 的指教 特此更正 如下:
    這種情況完全是因爲GC回收操作的不可預測性導致的。GC Heap上的對象生存期完全看GC是否要回收它而決定。此外,值類型完全沒必要實現 IDisposable 接口。
 
    總結:
          如果你的類中沒有用非託管資源,或者是非常大的實例(象 GIS 中的Geometry), 就沒有太大的必要實現這個接口. 並不是實現了這樣的接口就說明你寫的類有多大的不同或者會帶來多大的性能優勢.  

    本人對IDisposable接口就限於此,如果園友們有更多更好的理解都可以指教.

注:本人引用:http://hi.baidu.com/hygrom/blog/item/cc7f7f8da2e5f917b21bba70.html

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