.Net 的 IDisposable interface

來源:互聯網
上載者:User
.Net Framework 中的 Garbage Collection 會協助程式員自動回收託管資源,這對類庫的調用者而言,是個相當愜意的體驗:可以在任何位置,任何時候,建立任何對象,GC 最後總是會兜底。易地而處,當自己是類庫提供者的時候,則需要如何才能提供這樣良好的體驗呢?

首先,.Net framework 裡面哪些是託管的資源,哪些是非託管的資源?

基本上,在 .Net framework 裡面的所有類,都是託管資源,包括各種各樣的 stream(例如 FileStream, MemoryStream), database connection, components 等等。。

可以寫一個簡單的小程式驗證:(以 FileStream 為例)

一個方法,在後台線程中監控檔案是否正在被佔用:

        private static void MonitorFileStatus(string fileName)        {            Console.WriteLine("Start to monitor file: {0}", fileName);            Task.Factory.StartNew(() =>            {                while(true)                {                    bool isInUse = IsFileInUse(fileName);                    string messageFormat = isInUse ? "File {0} is in use." : "File {0} is released.";                    Console.WriteLine(messageFormat, fileName);                    Thread.Sleep(oneSeconds);                }            });        }        private static bool IsFileInUse(string fileName)        {            bool isInUse = true;            FileStream stream = null;            try            {                stream = File.Open(fileName, FileMode.Append, FileAccess.Write);                isInUse = false;            }            catch            {            }            finally            {                if (stream != null)                {                    stream.Dispose();                }            }            return isInUse;        }

再寫一個佔著檔案不用的方法, FileStream 只是個局部變數,這個方法返回的時候,它應該被回收:

        private static void OpenFile()        {            FileStream stream  = File.Open(TestFileName, FileMode.Append, FileAccess.Write);            Wait(fiveSeconds);        }

最後是一個必不可少的等待:

        private static void Wait(TimeSpan time)        {            Console.WriteLine("Wait for {0} seconds...", time.TotalSeconds);            Thread.Sleep(time);        }

合并起來就是一個測試:
首先開機檔案監視線程,然後開啟檔案不用。
OpenFile 方法返回,預測 FileStream 被回收
接著調用 GC, 看檔案是否被釋放了

        private static void FileTest()        {            MonitorFileStatus(TestFileName);            OpenFile();            CallGC();            Wait(fiveSeconds);        }

運行結果,可見 GC 自動把 FileStream 自動回收。無須調用 Dispose 方法,也無須使用 using

那麼,非託管資源套件括哪些呢?

通常,涉及到 windows api 的 pinvoke,各種的 intptr 都是非託管資源。例如,同樣是開啟檔案,如果寫成以下的樣子,就包括了非託管資源

    [Flags]    internal enum OpenFileStyle : uint    {        OF_CANCEL = 0x00000800,  // Ignored. For a dialog box with a Cancel button, use OF_PROMPT.        OF_CREATE = 0x00001000,  // Creates a new file. If file exists, it is truncated to zero (0) length.        OF_DELETE = 0x00000200,  // Deletes a file.        OF_EXIST = 0x00004000,  // Opens a file and then closes it. Used to test that a file exists        OF_PARSE = 0x00000100,  // Fills the OFSTRUCT structure, but does not do anything else.        OF_PROMPT = 0x00002000,  // Displays a dialog box if a requested file does not exist         OF_READ = 0x00000000,  // Opens a file for reading only.        OF_READWRITE = 0x00000002,  // Opens a file with read/write permissions.        OF_REOPEN = 0x00008000,  // Opens a file by using information in the reopen buffer.        // For MS-DOS–based file systems, opens a file with compatibility mode, allows any process on a         // specified computer to open the file any number of times.        // Other efforts to open a file with other sharing modes fail. This flag is mapped to the         // FILE_SHARE_READ|FILE_SHARE_WRITE flags of the CreateFile function.        OF_SHARE_COMPAT = 0x00000000,        // Opens a file without denying read or write access to other processes.        // On MS-DOS-based file systems, if the file has been opened in compatibility mode        // by any other process, the function fails.        // This flag is mapped to the FILE_SHARE_READ|FILE_SHARE_WRITE flags of the CreateFile function.        OF_SHARE_DENY_NONE = 0x00000040,        // Opens a file and denies read access to other processes.        // On MS-DOS-based file systems, if the file has been opened in compatibility mode,        // or for read access by any other process, the function fails.        // This flag is mapped to the FILE_SHARE_WRITE flag of the CreateFile function.        OF_SHARE_DENY_READ = 0x00000030,        // Opens a file and denies write access to other processes.        // On MS-DOS-based file systems, if a file has been opened in compatibility mode,        // or for write access by any other process, the function fails.        // This flag is mapped to the FILE_SHARE_READ flag of the CreateFile function.        OF_SHARE_DENY_WRITE = 0x00000020,        // Opens a file with exclusive mode, and denies both read/write access to other processes.        // If a file has been opened in any other mode for read/write access, even by the current process,        // the function fails.        OF_SHARE_EXCLUSIVE = 0x00000010,        // Verifies that the date and time of a file are the same as when it was opened previously.        // This is useful as an extra check for read-only files.        OF_VERIFY = 0x00000400,        // Opens a file for write access only.        OF_WRITE = 0x00000001    }    [StructLayout(LayoutKind.Sequential)]    internal struct OFSTRUCT    {        public byte cBytes;        public byte fFixedDisc;        public UInt16 nErrCode;        public UInt16 Reserved1;        public UInt16 Reserved2;        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]        public string szPathName;    }    class WindowsApi    {        [DllImport("kernel32.dll", BestFitMapping = false, ThrowOnUnmappableChar = true)]        internal static extern IntPtr OpenFile([MarshalAs(UnmanagedType.LPStr)]string lpFileName, out OFSTRUCT lpReOpenBuff, OpenFileStyle uStyle);        [DllImport("kernel32.dll", SetLastError = true)]        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]        [SuppressUnmanagedCodeSecurity]        [return: MarshalAs(UnmanagedType.Bool)]        internal static extern bool CloseHandle(IntPtr hObject);    }

處理非託管資源,需要實現 IDisposable interface。原因有兩個:

不能依賴解構函式,因為異構函數的調用由 GC 決定。無法即時釋放緊缺的資源。

有一通用的處理原則:解構函式處理託管資源,IDisposable interface 處理託管與非託管資源。

如上述的例子,完成的實現代碼如下:

    public class UnmanagedFileHolder : IFileHolder, IDisposable    {        private IntPtr _handle;        private string _fileName;        public UnmanagedFileHolder(string fileName)        {            _fileName = fileName;        }        public void OpenFile()        {            Console.WriteLine("Open file with windows api.");            OFSTRUCT info;            _handle = WindowsApi.OpenFile(_fileName, out info, OpenFileStyle.OF_READWRITE);        }        #region IDisposable Support        private bool disposed = false;        protected virtual void Dispose(bool disposing)        {            if (!disposed)            {                if (disposing)                {                    // no managed resource                }                WindowsApi.CloseHandle(_handle);                _handle = IntPtr.Zero;                disposed = true;            }        }        ~UnmanagedFileHolder()        {            Dispose(false);        }        public void Dispose()        {            Dispose(true);            GC.SuppressFinalize(this);        }        #endregion    }

如果同一個類裡面既有託管資源,也有非託管資源,那樣應該怎麼辦呢?

可以依照下面的模式:

    class HybridPattern : IDisposable    {        private bool _disposed = false;        ~HybridPattern()        {            Dispose(false);        }        protected void Dispose(bool disposing)        {            if (_disposed)            {                return;            }            if (disposing)            {                // Code to dispose the managed resources of the class                // internalComponent1.Dispose();            }            // Code to dispose the un-managed resources of the class            // CloseHandle(handle);            // handle = IntPtr.Zero;            _disposed = true;        }        public void Dispose()        {            Dispose(true);            GC.SuppressFinalize(this);        }    }

以下為完整的例子,有託管的 FileStream, 以及非託管的 Handler

    public class HybridHolder : IFileHolder, IDisposable    {        private string _unmanagedFile;        private string _managedFile;        private IntPtr _handle;        private FileStream _stream;        public HybridHolder(string unmanagedFile, string managedFile)        {            _unmanagedFile = unmanagedFile;            _managedFile = managedFile;        }        public void OpenFile()        {            Console.WriteLine("Open file with windows api.");            OFSTRUCT info;            _handle = WindowsApi.OpenFile(_unmanagedFile, out info, OpenFileStyle.OF_READWRITE);            Console.WriteLine("Open file with .Net libray.");            _stream = File.Open(_managedFile, FileMode.Append, FileAccess.Write);        }        #region IDisposable Support        private bool disposed = false;        protected virtual void Dispose(bool disposing)        {            if (!disposed)            {                //Console.WriteLine("string is null? {0}", _stream == null);                if (disposing && _stream != null)                {                    Console.WriteLine("Clean up managed resource.");                    _stream.Dispose();                }                Console.WriteLine("Clean up unmanaged resource.");                WindowsApi.CloseHandle(_handle);                _handle = IntPtr.Zero;                disposed = true;            }        }        ~HybridHolder()        {            Dispose(false);        }        public void Dispose()        {            Dispose(true);            GC.SuppressFinalize(this);        }        #endregion    }

最後,如果是沒有實現 IDisposable interface 的類呢? 例如 byte[], StringBuilder

完全不要插手幹預它們的回收, GC 做得很好。
嘗試過在解構函式中把一個龐大的 byte[] 設定為 null,唯一的結果是導致它的回收被延遲到下一次 GC 周期。
原因也很簡單,每一次引用到會導致它的引用樹上的計數加一。。

完整代碼見 Github:

https://github.com/IGabriel/IDisposableSample

  • 相關文章

    聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.