.Net Framework のガベージ コレクションは、プログラマーが管理されたリソースを自動的にリサイクルするのに役立ちます。これは、クラス ライブラリの呼び出し元にとって非常に快適なエクスペリエンスです。どのようなオブジェクトでも、いつでもどこでも作成でき、最終的には GC によって常にすべての詳細が明らかになります。 。クラス ライブラリのプロバイダーとして、このような優れたエクスペリエンスを提供するにはどうすればよいでしょうか?
まず第一に、.Net Framework ではどのリソースが管理対象リソースであり、どのリソースが管理対象外リソースでしょうか?
基本的に、.Net Framework のすべてのクラスは、さまざまなストリーム (FileStream、MemoryStream など)、データベース接続、コンポーネントなどを含むマネージド リソースです。 。
検証するための簡単な小さなプログラムを作成できます: (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 インターフェイスを実装する必要があります。理由は 2 つあります:
異種関数の呼び出しは GC によって決定されるため、デストラクターに依存することはできません。希少なリソースをリアルタイムで解放することはできません。
一般的な処理原則があります。デストラクターはマネージド リソースを処理し、IDisposable インターフェイスはマネージド リソースとアンマネージド リソースを処理します。
上記の例と同様、完成した実装コードは以下の通りです:
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 インターフェイスを実装していないクラスの場合はどうなるでしょうか? たとえば、byte[]、StringBuilder
はそれらのリサイクルをまったく妨げず、GCはうまく機能します。
デストラクターで huge byte[] を null に設定しようとしましたが、その結果は、そのコレクションが次の GC サイクルまで遅延されるということでした。
その理由も非常に単純で、参照が行われるたびに、その参照ツリーのカウントが 1 ずつ増加します。 。
完全なコードについては Github を参照してください:
https://github.com/IGabriel/IDisposableSample