Heim >Backend-Entwicklung >C#.Net-Tutorial >.Net ist eine IDisposable-Schnittstelle
Die Garbage Collection im .Net Framework hilft Programmierern, verwaltete Ressourcen automatisch zu recyceln. Dies ist eine sehr angenehme Erfahrung für die Aufrufer der Klassenbibliothek: Jedes Objekt kann an jedem Ort und zu jeder Zeit erstellt werden, und der GC wird dies tun Werde immer die Wahrheit sagen. Wie können wir als Anbieter einer Klassenbibliothek eine so gute Erfahrung bieten?
Welche Ressourcen werden im .Net-Framework verwaltet und welche sind nicht verwaltete Ressourcen?
Grundsätzlich sind alle Klassen im .Net-Framework verwaltete Ressourcen, einschließlich verschiedener Streams (wie FileStream, MemoryStream), Datenbankverbindungen, Komponenten usw. .
Sie können ein einfaches kleines Programm zur Überprüfung schreiben: (Nehmen Sie FileStream als Beispiel)
Eine Methode, um zu überwachen, ob die Datei im Hintergrundthread belegt ist:
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; }
Schreiben Sie eine weitere nicht verwendete Methode, die die Datei belegt. Wenn diese Methode zurückkehrt, sollte sie recycelt werden:
private static void OpenFile() { FileStream stream = File.Open(TestFileName, FileMode.Append, FileAccess.Write); Wait(fiveSeconds); }
Schließlich gibt es eine wesentliche Wartezeit:
private static void Wait(TimeSpan time) { Console.WriteLine("Wait for {0} seconds...", time.TotalSeconds); Thread.Sleep(time); }
Die Kombination ist ein Test:
Starten Sie zuerst den Dateiüberwachungsthread und öffnen Sie dann die Datei, ohne sie zu verwenden.
Die OpenFile-Methode kehrt zurück und sagt voraus, dass der FileStream recycelt wird.
Rufen Sie dann den GC auf, um zu sehen, ob die Datei freigegeben wurde.
private static void FileTest() { MonitorFileStatus(TestFileName); OpenFile(); CallGC(); Wait(fiveSeconds); }
Die laufenden Ergebnisse zeigen, dass der GC den FileStream automatisch recycelt . Es besteht keine Notwendigkeit, die Dispose-Methode aufzurufen, und es besteht keine Notwendigkeit, using zu verwenden
Was beinhalten also nicht verwaltete Ressourcen?
Wenn es um Windows-API-Pinvoke geht, sind verschiedene Intptr normalerweise nicht verwaltete Ressourcen. Wenn Sie beispielsweise eine Datei wie folgt öffnen, enthält sie nicht verwaltete Ressourcen
[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); }
Um mit nicht verwalteten Ressourcen umzugehen, müssen Sie die IDisposable-Schnittstelle implementieren. Dafür gibt es zwei Gründe:
kann sich nicht auf Destruktoren verlassen, da der Aufruf heterogener Funktionen vom GC bestimmt wird. Knappe Ressourcen können nicht in Echtzeit freigegeben werden.
Es gibt ein allgemeines Verarbeitungsprinzip: Der Destruktor verwaltet verwaltete Ressourcen, die IDisposable-Schnittstelle verwaltet verwaltete und nicht verwaltete Ressourcen.
Wie im obigen Beispiel lautet der vollständige Implementierungscode wie folgt:
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 }
Was sollen wir tun, wenn in derselben Klasse sowohl verwaltete als auch nicht verwaltete Ressourcen vorhanden sind?
kann dem folgenden Muster folgen:
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); } }
Das Folgende ist ein vollständiges Beispiel mit verwaltetem FileStream und nicht verwaltetem 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 }
Abschließend: Was ist mit Klassen? die die IDisposable-Schnittstelle nicht implementieren? Beispielsweise beeinträchtigen Byte[] und StringBuilder
ihr Recycling überhaupt nicht, GC leistet gute Arbeit.
Ich habe versucht, ein großes Byte[] im Destruktor auf Null zu setzen. Das einzige Ergebnis war, dass seine Sammlung bis zum nächsten GC-Zyklus verzögert wurde.
Der Grund ist ebenfalls sehr einfach. Jedes Mal, wenn eine Referenz erstellt wird, wird die Anzahl in ihrem Referenzbaum um eins erhöht. .
Der vollständige Code ist auf Github zu finden:
https://github.com/IGabriel/IDisposableSample