Synopsis: | Implement IDisposable if a class uses unmanaged resources, owns disposable objects or subscribes to other objects |
Language: | C# |
Severity Level: | 2 |
Category: | Object lifecycle |
Description: |
A class should implement the
Implementing When implementing the Dispose pattern you should follow certain rules. Some rules depend on whether the class can be inherited from (i.e. it is not sealed) and whether the class holds only managed resources or also holds unmanaged resources. The following cases can be distinguished:
The first case can be considered the general case. If possible, prefer to use the Sealed class holding only managed resources because it results in the simplest code. The different cases will be explained next. Inheritable class holding managed and/or unmanaged resourcesPlease adhere to the following rules:
The following code snippet shows an example of an inheritable (non-sealed) class holding one non- public class MyDisposable : IDisposable { [System.Runtime.InteropServices.DllImport("gdi32.dll")] public static extern bool DeleteObject(IntPtr hObject); // just an example 'PInvoke' call for releasing a Win32 resource private SqlConnection connection; private IntPtr handle; public void Dispose() { // Deterministic call to Dispose(bool) Dispose(true); // Prevent the finalizer from being called GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing) { if (connection != null) { connection.Dispose(); connection = null; } } if (handle != IntPtr.Zero) { DeleteObject(handle); handle = IntPtr.Zero; } } ~MyDisposable() { // Non-deterministic call to Dispose(bool) Dispose(false); } }
The following code snippet shows an example of an inheritable (non-sealed) class holding one Derived class holding managed and/or unmanaged resourcespublic class MyDisposable : IDisposable { private readonly SqlConnection connection; private bool isDisposed = false; public MyDisposable(string connectionString) { connection = new SqlConnection(connectionString); } public void Dispose() { if (!isDisposed) { Dispose(true); isDisposed = true; } } protected virtual void Dispose(bool disposing) { if (disposing) { connection.Dispose(); } } }
If a class derives from a class implementing
The following is an example implementation of a class deriving from the Sealed class holding only managed resourcespublic class MyDerivedDisposable : MyDisposable { protected SqlConnection anotherConnection; protected override void Dispose(bool disposing) { if (disposing) { // Dispose or cleanup managed resources of this derived class only anotherConnection?.Dispose(); anotherConnection = null; } base.Dispose(disposing); } }
If your class should not be inherited from, you can specify your class as
The following is an example implementation of a sealed class holding only managed resources: Alternatively, when using apublic sealed class SealedManagedResource : IDisposable { private IDisposable connection; public void Dispose() { if (connection != null) { connection.Dispose(); connection = null; } } } readonly field:
Sealed classes holding both managed and unmanaged resourcespublic sealed class SealedManagedReadonlyResource : IDisposable { private readonly IDisposable resource; private bool isDisposed; public void Dispose() { if (!isDisposed) { resource.Dispose(); isDisposed = true; } } } In the scenario where the class is sealed, but also holds unmanaged resources, the code is somewhat more complex. Example code for this scenario: Sealed class holding managed objects that need to be shared with unmanaged codepublic sealed class MyDisposable : IDisposable { [System.Runtime.InteropServices.DllImport("gdi32.dll")] public static extern bool DeleteObject(IntPtr hObject); // just an example 'PInvoke' call for releasing a Win32 resource private IDisposable connection; private IntPtr handle; public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (disposing) { if (connection != null) { connection.Dispose(); connection = null; } } if (handle != IntPtr.Zero) { DeleteObject(handle); handle = IntPtr.Zero; } } ~MyDisposable() { Dispose(false); } }
When you need to share a managed object with unmanaged code, you need to 'pin' the managed object so that its location on the heap will not be changed by the garbage collector. The managed object will remain pinned during the lifetime of the class implementing Example code for this scenario: public sealed class SealedManagedObjectForUnmanagedCode : IDisposable { // The managed object can be any .NET type, including custom ones. private object managedObject; private GCHandle gcHandle; private IntPtr handle; public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } ~SealedManagedObjectForUnmanagedCode() { Debug.Assert(!gcHandle.IsAllocated); Dispose(false); } // Method name just an example, can appear anywhere in the class. private void CreateResource() { gcHandle = GCHandle.Alloc(managedObject, GCHandleType.Pinned); handle = gcHandle.AddrOfPinnedObject(); } private void Dispose(bool disposing) { if (gcHandle.IsAllocated) { gcHandle.Free(); handle = IntPtr.Zero; } if (disposing) { // Cleanup managed resources } } } |