Home > Article > Backend Development > .Net 的 IDisposable interface
The Garbage Collection in the .Net Framework will help programmers automatically recycle managed resources. This is a very pleasant experience for the callers of the class library: any object can be created at any location and at any time, and the GC will always eventually reveal all the details. How can we provide such a good experience when we are a class library provider?
First of all, which resources are managed and which are unmanaged resources in .Net framework?
Basically, all classes in the .Net framework are managed resources, including various streams (such as FileStream, MemoryStream), database connection, components, etc. .
You can write a simple small program to verify: (take FileStream as an example)
A method to monitor whether the file is being occupied in the background thread:
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; }
Write another method that occupies the file when it is not used, FileStream is just a partial Variable, when this method returns, it should be recycled:
private static void OpenFile() { FileStream stream = File.Open(TestFileName, FileMode.Append, FileAccess.Write); Wait(fiveSeconds); }
Finally, there is an essential wait:
private static void Wait(TimeSpan time) { Console.WriteLine("Wait for {0} seconds...", time.TotalSeconds); Thread.Sleep(time); }
Combined, it is a test:
First start the file monitoring thread, and then open the file.
The OpenFile method returns, predicting that the FileStream will be recycled
Then call the GC to see if the file has been released
private static void FileTest() { MonitorFileStatus(TestFileName); OpenFile(); CallGC(); Wait(fiveSeconds); }
The running results show that the GC automatically recycles the FileStream. There is no need to call the Dispose method or use using
So, what do unmanaged resources include?
Usually, when it comes to Windows API pinvoke, various intptr are unmanaged resources. For example, if you open a file as follows, it includes unmanaged resources
[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); }
To handle unmanaged resources, you need to implement the IDisposable interface. There are two reasons:
You cannot rely on destructors, because the call of heterogeneous functions is determined by the GC. Scarce resources cannot be released in real time.
There is a general processing principle: destructor handles managed resources, and IDisposable interface handles managed and unmanaged resources.
As in the above example, the completed implementation code is as follows:
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 }
What should we do if there are both managed resources and unmanaged resources in the same class?
You can follow the following pattern:
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); } }
The following is a complete example, with managed FileStream, and unmanaged 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 }
Finally, what if it is a class that does not implement IDisposable interface? For example, byte[], StringBuilder
don't interfere with their recycling at all, GC does a good job.
I tried setting a huge byte[] to null in the destructor. The only result was that its collection was delayed until the next GC cycle.
The reason is also very simple. Each time a reference is made, the count on its reference tree will be increased by one. .
See Github for the complete code:
https://github.com/IGabriel/IDisposableSample