Loading... C# 中有两种释放资源的方式:实现 `IDisposable` 或使用`析构函数`。通常,必须在特定时间释放资源的场景中,我们实现 `IDisposable`,像这样: ```csharp public class ExampleDispose : IDisposable { // 非托管资源 private IntPtr _handle; // 使用的其它托管资源 private readonly Stream _stream; private bool disposed = false; public ExampleDispose(Stream stream, IntPtr handle) { this._stream = stream; this._handle = handle; } public void Dispose() { if (disposed) { return; } disposed = true; // 调用其它托管资源的Dispose _stream.Dispose(); // 释放非托管资源 CloseHandle(_handle); _handle = IntPtr.Zero; } [System.Runtime.InteropServices.DllImport("Kernel32")] private extern static Boolean CloseHandle(IntPtr handle); } ``` 通常来说,如果你在使用完后正确调用了 `Dispose()` 方法或者使用了 `using`,就可以正确释放资源。 其他情况,则需要依赖 `GC` 在回收托管对象前先释放引用的非托管资源,一个实现了 `IDisposable` 接口和具有`析构函数`的类可能如下所示: ```csharp public class ExampleDispose : IDisposable { // 非托管资源 private IntPtr _handle; // 使用的其它托管资源 private readonly Stream _stream; private bool disposed = false; public ExampleDispose(Stream stream, IntPtr handle) { this._stream = stream; this._handle = handle; } ~ExampleDispose() { // 此代码有异常情况,不要在生产环境使用 Dispose(); } public void Dispose() { if (disposed) { return; } disposed = true; // 调用其它托管资源的Dispose _stream.Dispose(); // 释放非托管资源 CloseHandle(_handle); _handle = IntPtr.Zero; } [System.Runtime.InteropServices.DllImport("Kernel32")] private extern static Boolean CloseHandle(IntPtr handle); } ``` 相比上部分代码,只是多增加了一个析构函数 `~ExampleDispose()`,仔细观察它非常有意思,构造函数用于对象的创建,析构函数有点像构造函数,只是没有访问修饰符,且前面多了个 “`~`”,我们知道这个符号通常用于取反操作,构造函数取反,那就是“`销毁函数`”,也正如它所隐含的含义那样:`GC` 会在回收对象前调用析构函数来释放资源。现在,无论用户是否主动调用过 `Dispose()`,`~ExampleDispose()` 都会在 `GC` 回收对象时被调用,如果用户主动释放过资源,那么 `disposed` 为 `true`,`~ExampleDispose()` 实际上调用 `Dispose()` 后直接返回,没有多于的操作,但是另一种情况,用户从未主动调用过 `Dispose()`,这时候在 `GC` 在调用 `~ExampleDispose()` 时,会调用到 `_stream.Dispose();`。注意,这里可能会产问题,因为 `GC` 在回收对象时,对象内部其它对象可能已经被回收,这里 `_stream` 是有可能已经被回收的,因此,我们需要稍微更改一下,以实现在 `GC` 时不引用任何托管代码,为此,我们实现 [`dispose pattern`](https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-dispose): ```csharp public class ExampleDispose : IDisposable { // 非托管资源 private IntPtr _handle; // 使用的其它托管资源 private readonly Stream _stream; private bool disposed = false; public ExampleDispose(Stream stream, IntPtr handle) { this._stream = stream; this._handle = handle; } ~ExampleDispose() { Dispose(false); } public void Dispose() { Dispose(true); } // 如果 disposing 为 true, 则这是用户代码主动释放, // 可以安全的释放托管与非托管对象 // 反之,则为 `GC` 回收对象时调用,这时不可引用任何托管对象 protected virtual void Dispose(bool disposing) { if (disposed) { return; } disposed = true; if (disposing) { // 调用其它托管资源的Dispose _stream.Dispose(); } // 释放非托管资源 CloseHandle(_handle); _handle = IntPtr.Zero; } [System.Runtime.InteropServices.DllImport("Kernel32")] private extern static Boolean CloseHandle(IntPtr handle); } ``` 这样看起来,它已经能够正常工作了,但是还有个小问题:即使用户主动释放了资源,`GC` 还是会对 `~ExampleDispose()` 进行调用。我们再对代码进行一次小更改,实现用户主动释放资源后,使得 `GC` 回收对象时不再调用 `~ExampleDispose()`: ```csharp public class ExampleDispose : IDisposable { // 非托管资源 private IntPtr _handle; // 使用的其它托管资源 private readonly Stream _stream; private bool disposed = false; public ExampleDispose(Stream stream, IntPtr handle) { this._stream = stream; this._handle = handle; } ~ExampleDispose() { Dispose(false); } public void Dispose() { Dispose(true); // 调用 GC.SuppressFinalize 将该对象从终结队列中移除, // 并防止该对象的终结代码再次执行。 GC.SuppressFinalize(this); } // 如果 disposing 为 true, 则这是用户代码主动释放, // 可以安全的释放托管与非托管对象 // 反之,则为 `GC` 回收对象时调用,这时不可引用任何托管对象 protected virtual void Dispose(bool disposing) { if (disposed) { return; } disposed = true; if (disposing) { // 调用其它托管资源的Dispose _stream.Dispose(); } // 释放非托管资源 CloseHandle(_handle); _handle = IntPtr.Zero; } [System.Runtime.InteropServices.DllImport("Kernel32")] private extern static Boolean CloseHandle(IntPtr handle); } ``` # Reference * [Implement a Dispose method](https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-dispose) * [GC Class](https://docs.microsoft.com/en-us/dotnet/api/system.gc?view=net-6.0) * [Object.Finalize Method](https://docs.microsoft.com/en-us/dotnet/api/system.object.finalize?view=net-6.0) * [IDisposable Interface](https://docs.microsoft.com/en-us/dotnet/api/system.idisposable?view=net-6.0) 最后修改:2022 年 08 月 24 日 © 允许规范转载 赞 0 如果觉得我的文章对你有用,请随意赞赏