清理非託管資源

通過將對象的範圍限制爲 protected,可以防止應用程序用戶直接調用對象的 Finalize 方法。除此之外,強烈建議不要直接調用非基類類的 Finalize 方法。

爲了適時地釋放非託管資源,建議實現公共的 Dispose  Close 方法,爲對象執行必要的清理代碼操作。IDisposable 接口爲實現接口的資源類提供 Dispose 方法。因爲 Dispose 方法是公共的,所以應用程序用戶可以直接調用該方法來釋放非託管資源佔用的內存。當正確實現 Dispose 方法,在未能調用 Dispose 方法的情況下,Finalize 方法充當防護措施來清理資源。

實現 Dispose 方法

描述實現Dispose 方法用於釋放非託管資源。

釋放對象的模式(稱爲釋放模式dispose pattern)對對象的生存期進行規定。釋放方案僅用於非託管資源的對象。因爲垃圾回收器對回收未使用的託管對象非常有效。

一個類型的 Dispose 方法應釋放它擁有的所有資源,它還應該通過調用其父類型的 Dispose 方法來釋放其基類型擁有的所有資源。父類型的 Dispose 方法應該釋放它擁有的所有資源,進而調用其父類型的 Dispose 方法,從而在整個基類型層次結構中傳播此模式。若要確保資源始終被正確地清理Dispose 方法應該可以多次調用,而不引發異常。

對只使用託管資源的類型(如數組arrays)實現 Dispose 方法並不能提高性能,因爲這些類型由垃圾回收器自動回收。應主要對使用本機資源的託管對象(如FileStream )和向 .NET Framework 公開的 COM 對象使用Dispose 方法。

Dispose 方法應該調用 SuppressFinalize 方法來釋放對象。如果對象當前在終止隊列中,則 SuppressFinalize 會阻止調用其 Finalize 方法。記住,執行 Finalize 方法會降低性能。如果Dispose 方法已完成清理對象的工作,垃圾回收器就不必調用對象的 Finalize 方法。

GC.KeepAlive 方法提供的代碼示例演示了,強行垃圾回收如何在回收對象的成員仍在執行時引發終結器(finalizer)運行。在較長的 Dispose 方法最後,最好調用 KeepAlive 方法。

SafeHandle 可選

編寫一個對象的終結器代碼finalizer是一個複雜的任務,如果沒有正確完成,可能會導致問題。因此,我們建議構造 SafeHandle對象,而不是實現釋放模式(dispose pattern)。

SafeHandle類通過分配和不中斷的釋放句柄,簡化對象生存期問題。它包含一個關鍵的終結器,保證在應用程序域正在卸載時運行。

SafeHandle類位於 System.Runtime.InteropServices命名空間,它是一個抽象的包裝類,用來操作系統的句柄。從該類派生(繼承)是很困難的,但可以使用 Microsoft.Win32.SafeHandles命名空間下的派生類,它提供以下安全的句柄:

1)        文件和管道。

2)        內存視圖。

3)        加密構造。

4)        註冊表項。

5)        等待句柄。

示例

下面代碼演示爲封裝非託管資源的類實現 Dispose 方法的推薦設計模式。

資源類通常是從複雜的本機類或 API 派生的,而且必須進行相應的自定義。使用這一代碼模式作爲創建資源類的一個起始點,並根據封裝的資源提供必要的自定義。

using System;
using System.IO;
namespace AMConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                // 初始化一個 Stream 資源,把它傳給 DisposableResource 類
                Console.Write("Enter filename and its path: ");
                string fileSpec = Console.ReadLine();
                FileStream fs = File.OpenRead(fileSpec);
                DisposableResource TestObj = new DisposableResource(fs);
                // 使用資源
                TestObj.DoSomethingWithResource();
                // 釋放資源            
                TestObj.Dispose();
                Console.ReadKey();

            }
            catch (FileNotFoundException e)
            {
                Console.WriteLine(e.Message);
                Console.ReadKey();
            }
        }
        /// <summary>     
        /// 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.    
        /// </summary>        
        public class DisposableResource : IDisposable
        {
            private Stream _resource;
            private bool _disposed;
            /// <summary>          
            /// 將 stream 傳給構造函數,它必須可讀、非空     
            /// </summary>        
            /// <param name="stream"></param>    
            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;
            }
            /// <summary>    
            /// 驗證資源的使用. 它必須沒有被釋放     
            /// </summary>            
            public void DoSomethingWithResource()
            {
                if (_disposed)
                    throw new ObjectDisposedException("Resource was disposed.");
                // 顯示字節數            
                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)
            {
                // 如果需要線程安全,請在這個操作,以及使用資源方法中使用 lock     
                if (!_disposed)
                {
                    if (disposing)
                    {
                        if (_resource != null)
                            _resource.Dispose();
                        Console.WriteLine("Object disposed.");
                    }
                    // Indicate that the instance has been disposed. 
                    _resource = null;
                    _disposed = true;
                }
            }
        }
    }
}
說明

1)        定義DisposableResource類,從外部獲得文件流,讀取其大小,最後釋放其文件流的資源。該類繼承IDisposable接口,並實現Dispose()方法;

2)        DisposableResource類的成員函數DoSomethingWithResource獲得文件流的大小;

3)        DisposableResource類的成員函數Dispose極其重載,負責釋放資源。

IDisposable 接口

定義一種釋放分配的資源的方法。

此接口的主要用途是釋放非託管資源。當不再使用託管對象時,垃圾回收器會自動釋放分配給該對象的內存。但無法預測進行垃圾回收的時間。另外,垃圾回收器對窗口句柄或打開的文件和流等非託管資源一無所知。

將此接口的 Dispose 方法與垃圾回收器一起使用來顯式釋放非託管資源。當不再需要對象時,對象的使用者可以調用此方法。

向現有類添加 IDisposable 接口是重大的更改,因爲這更改了類的語義。

有關如何使用此接口和 Object.Finalize 方法的詳細討論。

在調用可實現 IDisposable 接口的類時,請使用 try/finally 模式來確保非託管資源能夠釋放出來,即使應用程序因出現異常而中斷也是如此。

有關 try/finally 模式的更多信息,請參見try-finallyC# 參考) Thetry-finally Statement

請注意,您可以使用 using 語句(在 Visual Basic 中爲 Using)來代替 try/finally 模式。有關更多信息,請參見 Using 語句 (Visual Basic) 文檔或 using 語句(C# 參考)文檔。

示例

下面的示例演示如何創建用來實現 IDisposable 接口的資源類。

using System;
using System.ComponentModel;
public class DisposeExample
{
    /// <summary>
    /// A base class that implements IDisposable. 
    /// By implementing IDisposable, you are announcing that  instances of this type allocate scarce resources.   
    /// </summary>
    public class MyResource : IDisposable
    {
        // 指向外部非託管資源(external unmanaged resource)    
        private IntPtr handle;
        // 類中使用其他託管資源      
        private Component component = new Component();
        // 追蹤 Dispose 是否被調用    
        private bool disposed = false;

        public MyResource(IntPtr handle)
        {
            this.handle = handle;
        }
        /// <summary>
        /// 實現 IDisposable    
        /// </summary>
        /// <remarks>
        /// 不要將該方法設置爲 virtual,這樣,它的子類就不會 override 這個方法       
        /// </remarks>
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method.      
            // Therefore, you should call GC.SupressFinalize to        
            // take this object off the finalization queue        
            // and prevent finalization code for this object from executing a second time.     
            GC.SuppressFinalize(this);
        }
        /// <summary>
        /// 在兩個常見的情況下執行 Dispose(bool disposing)   
        /// </summary>
        /// <remarks>
        /// 如果 disposing 爲 true,那麼這個方法直接或間接地被用戶代碼調用,託管和非託管資源會被釋放。
        /// 如果 disposing 爲 false,那麼這個方法會在終結器(finalizer)內被運行時調用,你不能引用其他對象。
        /// 只有非託管資源纔可以被釋放。  
        /// </remarks>
        /// <param name="disposing"></param>
        private void Dispose(bool disposing)
        {
            // 檢查 Dispose 方法是否被調用    
            if (!this.disposed)
            {
                // 如果 disposing 爲 true,那麼釋放所有的託管和非託管資源     
                if (disposing)
                {
                    // 釋放託管資源   
                    component.Dispose();
                }
                // 調用適當的方法,清除非託管資源             
                // 如果 disposing 爲 false,那麼僅執行下面代碼    
                CloseHandle(handle);
                handle = IntPtr.Zero;
                // 釋放完成       
                disposed = true;
            }
        }
        /// <summary>
        /// 使用 interop 來調用需要清除非託管資源的方法
        /// </summary>
        /// <param name="handle"></param>
        /// <returns></returns>
        [System.Runtime.InteropServices.DllImport("Kernel32")]
        private extern static Boolean CloseHandle(IntPtr handle);
        /// <summary>
        /// Use C# destructor syntax for finalization code.  
        /// </summary>
        /// <remarks>
        /// This destructor will run only if the Dispose method does not get called.    
        /// It gives your base class the opportunity to finalize.  
        /// Do not provide destructors in types derived from this class. 
        /// </remarks>
        ~MyResource()
        {
            // Do not re-create Dispose clean-up code here.     
            // Calling Dispose(false) is optimal in terms of readability and maintainability.          
            Dispose(false);
        }
    }
    public static void Main()
    {
        // Insert code here to create and use the MyResource object.
    }
}

Finalize 方法和析構函數 

描述 Finalize 方法和析構函數如何允許一個對象在垃圾回收器自動回收對象內存之前執行必要的清理操作。

對於您應用程序創建的大多數對象,可以依靠 .NET Framework 垃圾回收器隱式地執行所有必要的內存管理任務。但是,在創建封裝非託管資源的對象時,在應用程序中使用完這些非託管資源後,必須顯式地釋放它們。最常見的一類非託管資源就是包裝操作系統資源的對象,例如文件、窗口或網絡連接。雖然垃圾回收器可以跟蹤封裝非託管資源的對象的生存期,但它不瞭解具體如何清理這些資源。對於這些類型的對象,.NET Framework 提供 Object.Finalize 方法,它允許對象在垃圾回收器回收該對象使用的內存時適當清理其非託管資源。默認情況下,Finalize 方法不執行任何操作。如果您要讓垃圾回收器在回收對象的內存之前對對象執行清理操作,必須在類中重寫 Finalize 方法。

若要在 C# 中實現 Finalize 方法,必須使用析構函數語法。無法從 C# C++ 編程語言中調用或重寫 Object.Finalize方法,C# 將析構函數用作編寫終止代碼的機制。

垃圾回收器使用名爲“終止隊列”的內部結構跟蹤具有 Finalize 方法的對象。每次應用程序創建具有 Finalize 方法的對象時,垃圾回收器都在終止隊列中放置一個指向該對象的項。託管堆中所有需要在垃圾回收器回收其內存之前調用它們的終止代碼的對象都在終止隊列中含有項。

注意:爲 GC.KeepAlive 方法提供的代碼示例演示,攻擊性垃圾回收如何會導致終結器在已回收的對象的成員仍在執行時運行,以及如何使用 KeepAlive 方法來阻止這種情況的發生。

Finalize 方法不應引發異常,因爲應用程序無法處理這些異常,而且這些異常會導致應用程序終止。

實現 Finalize 方法或析構函數對性能可能會有負面影響,應避免不必要地使用。用 Finalize 方法回收對象使用的內存,需要至少兩次垃圾回收。當垃圾回收器執行回收時,它只回收沒有終結器的、不可訪問對象的內存,不能回收具有終結器的、不可訪問的對象。它改爲將這些對象的項從終止隊列中移除並將它們放置在標爲準備終止的對象列表中。該列表中的項指向託管堆中準備被調用其終止代碼的對象。垃圾回收器爲此列表中的對象調用 Finalize 方法,然後,將這些項從列表中移除。後來的垃圾回收將確定終止的對象確實是垃圾,因爲標爲準備終止對象的列表中的項不再指向它們。在後來的垃圾回收中,實際上回收了對象的內存。

參考: C#析構函數destructor和終結器Finalizer http://blog.csdn.net/liuning800203/archive/2011/05/30/6455226.aspx

重寫 Finalize 方法

描述 Finalize  Dispose 方法一起使用的方式。

Finalize 方法在未能調用 Dispose 方法的情況下充當防護措施來清理資源。 您應該只實現 Finalize 方法來清理非託管資源。 您不應該對託管對象實現 Finalize 方法,因爲垃圾回收器會自動清理託管資源。 默認情況下,Object.Finalize 方法不進行任何操作。 如果要讓垃圾回收器在回收對象的內存之前對對象執行清理操作,您必須在類中重寫此方法。

若要在 C# 中實現 Finalize 方法,必須使用析構函數語法

Object.Finalize 方法的範圍是受保護的。 當在類中重寫該方法時,您應該保持這個有限的範圍。 通過保護 Finalize 方法,您可以防止應用程序的用戶直接調用對象的 Finalize 方法。

對象的 Finalize 方法應該釋放該對象保留的所有資源。 它還應該調用該對象基類的 Finalize 方法。 對象的 Finalize 方法不應對任何非其基類的對象調用方法。 這是因爲被調用的其他對象可能和調用對象在同一時間被回收,例如公共語言運行時關閉這種情況。

如果您允許任何異常避開 Finalize 方法,系統將認爲方法返回,並繼續調用其他對象的 Finalize 方法。

C# C++ 中的析構函數語法

描述 Finalize 方法在 C# C++ 中的等效方法。

 


使用封裝資源的對象

描述確保 Dispose 方法被調用的方式,例如 C# using 語句(在 Visual Basic 中爲 Using)。

如果您要編寫代碼,而該代碼使用一個封裝資源的對象,您應該確保在使用完該對象時調用該對象的 Dispose 方法。 要做到這一點,可以使用 C#  using 語句,或使用其他面向公共語言運行時的語言來實現 try/finally 塊。

C# Using 語句

C# 編程語言的 using 語句通過簡化必須編寫以便創建和清理對象的代碼,使得對 Dispose 方法的調用更加自動化。 using 語句獲得一個或多個資源,執行您指定的語句,然後處置對象。 請注意,using 語句只適用於這樣的對象:這些對象的生存期不超過在其中構建這些對象的方法。 下面的代碼示例將創建並清理 ResourceWrapper 類的實例,如 C# 示例實現Dispose 方法中所示。

class myApp
{
    public static void Main()
    {
        using (ResourceWrapper r1 = new ResourceWrapper())
        {
            // Do something with the object.       
            r1.DoSomething();
        }
    }
}
與下面的代碼等效。

class myApp
{
    public static void Main()
    {
        ResourceWrapper r1 = new ResourceWrapper();
        try
        {
            // Do something with the object.      
            r1.DoSomething();
        }
        finally
        {
            // Check for a null resource.  
            if (r1 != null)
                // Call the object's Dispose method.    
                r1.Dispose();
        }
    }
}
使用 C#  using 語句,可以在單個語句(該語句在內部同嵌套的 using 語句是等效的)中獲取多個資源。

Try/Finally

當您用 C# 以外的語言編寫託管代碼時,如果該代碼使用一個封裝資源的對象,請使用 try/finally 塊來確保調用該對象的 Dispose 方法。下面的代碼示例將創建並清理 Resource 類的實例

class myApp   
    Public Shared Sub Main()  
        Resource r1 = new Resource()   
        Try      
            ' Do something with the object.
            r1.DoSomething()    
        Finally        
            ' Check for a null resource.  
            If Not (r1 is Nothing) Then    
                ' Call the object's Dispose method.      
                r1.Dispose()      
            End If   
        End Try 
    End SubEnd
Class

原文地址:http://msdn.microsoft.com/zh-cn/library/498928w2.aspx

 

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