When working with .NET, you’ll often hear about the garbage collector and how it manages the allocation and release the application’s memory from the managed heap, making our life easier.
This is true for the majority of objects but sometimes we have to work with unmanaged resources — files, network or database connections — that we must explicitly release since the garbage collector doesn’t know how to do the cleanup for us, despite being able to track the object that encapsulates the unmanaged resource.
To help preventing memory leaks, .NET provides a simple and standard way to cleanup unmanaged resources called dispose pattern, which consists in implementing the IDisposable
interface and calling Dispose method when resources aren’t needed, which is usually done via the using
keyword.
1 | using (var file = File.OpenText(filePath)) |
When .NET Core 3.0 was released, Microsoft introduced the interface IAsyncDisposable
to allow for asynchronous cleanup operations by calling the DisposeAsync
method, usually done with await using
keywords.
1 | await using (var x = new SomeAsyncDisposableClass()) |
If a class implements IAsyncDisposable
it’s usually a good practice to also implement IDisposable
despite not being a requirement. Not all code runs asynchronously, so this ensures the class is ready for both scenarios.
In this article I’m going to demonstrate and explain step by step how to properly implement the dispose pattern using both IDisposable
and IAsyncDisposable
interfaces.
Example scenario
To use as an example, we are going to implement a class that wraps SQL Server connections and uses Dapper to simplify mappings and every time someone queries the database it will write the SQL instruction to the log. This provides a simple example of a class that holds both managed and unmanaged resources that can also be disposed asynchronously — SQL Server network connection.
The class without any disposable implementations:
1 | public class SqlServerQueryRunner |
As you can see, this class receives a connection string and creates a SQL Server connection. Because this class is the owner of the SqlConnection
instance, it must dispose it or else resources won’t be released.
IDisposable interface
Implement the IDisposable
interface by disposing unmanaged resources inside the Dispose
method, in this case the SQL Server connection:
1 | public class SqlServerQueryRunner : IDisposable |
This code looks good and will work as expected, but there are some scenarios to consider:
- Will the class be sealed? If not, we must provide a way for extenders to cleanup their unmanaged resources, if any;
- Do I want to a safeguard from forgetting to dispose? If yes, we must implement a class finalizer that will try to cleanup resources when garbage collected;
Both scenarios are solved by creating a virtual Dispose method, to be overridden by extenders, that receives a flag to indicate if it’s being called from a dispose or finalizer.
This usually means resources are explicitly disposed only when disposing, otherwise references are cleared and we assume unmanaged resources also have finalizers.
1 | public class SqlServerQueryRunner : IDisposable |
You now may be asking: But if we have finalizers that will be triggered when the class is garbage collected, why implement IDisposable
?
That’s a fair question with a simple answer: You want garbage collection to be as fast as possible because it is a synchronous operation that stops all processes while it’s running.
This also means unmanaged resources wouldn’t be released until garbage collection happened, which may take some time — imagine a reference to a file that you didn’t need but the process would still be locking it, now you want to open it again but can’t, because the garbage collector haven’t run yet.
If you look at the Dispose
method, you’ll realize there’s a call to GC.SuppressFinalize
, which is a method that actually tells the garbage collector: look, this class has a finalizer but you can ignore it because it is irrelevant, the developer already released resources using the dispose pattern.
IAsyncDisposable interface
Implementing the IAsyncDisposable
interface is very similar to IDisposable
with a small difference, we call Dispose(false)
inside the method DisposeAsync
to keep functional equivalence with the synchronous dispose pattern and further ensuring that finalizer code paths are still invoked. There’s no need to dispose resources synchronously if they were already disposed asynchronously.
1 | public class SqlServerQueryRunner : IDisposable, IAsyncDisposable |
As a small note, if the class was sealed, the method DisposeAsyncCore
wouldn’t be needed and all code could be inside the DisposeAsync
method.
ObjectDisposedException
When implementing a disposable class it’s also a good practice to throw an ObjectDisposedException
when it’s been disposed but some code is still trying to use it.
This usually is as simple as creating a flag to be set by the Dispose(bool)
method and checking for it on all methods that may apply.
1 | public class SqlServerQueryRunner : IDisposable, IAsyncDisposable |
Conclusion
In this article I explained the dispose pattern in .NET, which is used to cleanup and release unmanaged resources from memory, either synchronously by implementing the IDisposable
interface, or asynchronously with IAsyncDisposable
.
For the example we used a simple wrapper for SqlConnection
that holds references to both unmanaged and managed resources, making sure both were properly cleared and an ObjectDisposedException
was thrown if the class was used after being disposed.
1 | public class SqlServerQueryRunner : IDisposable, IAsyncDisposable |