在类型使用者忘记调用 Dispose 的情况下,使用安全句柄包装非托管资源。 这是推荐采用的方法。 安全句柄派生自 System.Runtime.InteropServices.SafeHandle 抽象类,并包含可靠的 Finalize 方法。 在使用安全句柄时,只需实现 IDisposable 接口并在 Dispose 实现中调用安全句柄的 IDisposable.Dispose 方法。 如果未调用安全句柄的 Dispose 方法,则垃圾回收器将自动调用安全句柄的终结器。
Microsoft.Win32.SafeHandles 命名空间中的以下派生类提供安全句柄:
- 用于文件、内存映射文件和管道的 SafeFileHandle、SafeMemoryMappedFileHandle 和 SafePipeHandle 类。
- 用于内存视图的 SafeMemoryMappedViewHandle 类。
- 用于加密构造的 SafeNCryptKeyHandle、SafeNCryptProviderHandle 和 SafeNCryptSecretHandle 类。
- 用于注册表项的 SafeRegistryHandle 类。
- 用于等待句柄的 SafeWaitHandle 类。
Dispose()
IDisposable 接口需要实现单个无参数的方法 Dispose。 此外,任何非密封类都应具有要实现的附加 Dispose(bool)
重载方法
无参数的 Dispose
方法由该类型的使用者调用,因此其用途是释放非托管资源,执行常规清理,以及指示终结器(如果存在)不必运行。 释放与托管对象关联的实际内存始终是垃圾回收器的域。 因此,它具有标准实现:
public void Dispose() { // Dispose of unmanaged resources. Dispose(true); // Suppress finalization. GC.SuppressFinalize(this); }
disposing
参数是一个 Boolean,它指示方法调用是来自 Dispose 方法(其值为 true
)还是来自终结器(其值为 false
)。
方法的主体包含两个代码块:
-
释放非托管资源的块。 无论
disposing
参数的值如何,都会执行此块。 -
释放托管资源的条件块。 如果
disposing
的值为true
,则执行此块。 它释放的托管资源可包括:-
实现 IDisposable 的托管对象。 可用于调用其 Dispose 实现(级联释放)的条件块。 如果你已使用 System.Runtime.InteropServices.SafeHandle 的派生类来包装非托管资源,则应在此处调用 SafeHandle.Dispose() 实现。
-
占用大量内存或使用短缺资源的托管对象。 将大型托管对象引用分配到
null
,使它们更有可能无法访问。 相比以非确定性方式回收它们,这样做释放的速度更快。
-
如果方法调用来自终结器,则应仅执行释放非托管资源的代码。 实施者负责确保假路径不会与可能已被回收的托管对象交互。 这一点很重要,因为垃圾回收器在终止期间销毁托管对象的顺序是不确定的。
使用安全句柄实现释放模式
using Microsoft.Win32.SafeHandles; using System; using System.Runtime.InteropServices; public class DisposableStreamResource : IDisposable { // Define constants. protected const uint GENERIC_READ = 0x80000000; protected const uint FILE_SHARE_READ = 0x00000001; protected const uint OPEN_EXISTING = 3; protected const uint FILE_ATTRIBUTE_NORMAL = 0x80; private const int INVALID_FILE_SIZE = unchecked((int)0xFFFFFFFF); // Define Windows APIs. [DllImport("kernel32.dll", EntryPoint = "CreateFileW", CharSet = CharSet.Unicode)] protected static extern SafeFileHandle CreateFile( string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile); [DllImport("kernel32.dll")] private static extern int GetFileSize( SafeFileHandle hFile, out int lpFileSizeHigh); // Define locals. private bool _disposed = false; private readonly SafeFileHandle _safeHandle; private readonly int _upperWord; public DisposableStreamResource(string fileName) { if (string.IsNullOrWhiteSpace(fileName)) { throw new ArgumentException("The fileName cannot be null or an empty string"); } _safeHandle = CreateFile( fileName, GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero); // Get file size. Size = GetFileSize(_safeHandle, out _upperWord); if (Size == INVALID_FILE_SIZE) { Size = -1; } else if (_upperWord > 0) { Size = (((long)_upperWord) << 32) + Size; } } public long Size { get; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (_disposed) { return; } // Dispose of managed resources here. if (disposing) { _safeHandle?.Dispose(); } // Dispose of any unmanaged resources not wrapped in safe handles. _disposed = true; } }
DisposeAsync()
无参数的 DisposeAsync()
方法在 await using
语句中隐式调用,它具有标准实现:
public async ValueTask DisposeAsync() { // Perform async cleanup. await DisposeAsyncCore(); // Dispose of managed resources. Dispose(false); // Suppress finalization. GC.SuppressFinalize(this); }
异步释放模式的主要差异在于,从 DisposeAsync() 到 Dispose(bool)
重载方法的调用被赋予 false
作为参数。 但实现 IDisposable.Dispose() 方法时,改为传递 true
。 换句话说,DisposeAsyncCore()
方法将异步释放托管资源,因此不希望也同步释放这些资源。 因此,调用 Dispose(false)
而非 Dispose(true)
。
using System; using System.Text.Json; using System.Threading.Tasks; public class ExampleAsyncDisposable : IAsyncDisposable, IDisposable { // To detect redundant calls private bool _disposed = false; // Created in .ctor, omitted for brevity. private Utf8JsonWriter _jsonWriter; public async ValueTask DisposeAsync() { await DisposeAsyncCore(); Dispose(false); GC.SuppressFinalize(this); } protected virtual async ValueTask DisposeAsyncCore() { // Cascade async dispose calls if (_jsonWriter != null) { await _jsonWriter.DisposeAsync(); _jsonWriter = null; } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (_disposed) { return; } if (disposing) { _jsonWriter?.Dispose(); // TODO: dispose managed state (managed objects). } // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. // TODO: set large fields to null. _disposed = true; } }
堆叠的 using
在创建和使用实现 IAsyncDisposable 的多个对象的情况下,残存错误条件中堆叠的 using
语句可能会阻止调用 DisposeAsync()。 为了帮助防止潜在的问题,应避免堆叠,并遵循以下示例模式:
class ExampleProgram { static async Task Main() { var objOne = new ExampleAsyncDisposable(); await using objOne.ConfigureAwait(false); // Interact with the objOne instance. var objTwo = new ExampleAsyncDisposable(); await using objTwo.ConfigureAwait(false)) { // Interact with the objTwo instance. } Console.ReadLine(); } }
使用 C# using
语句,还可以在一个语句(在内部相当于嵌套语句 using
)中获取多个资源。 下面的示例实例化两个 StreamReader 对象以读取两个不同文件的内容。
using StreamReader version1 = new StreamReader("file1.txt"), version2 = new StreamReader("file2.txt");