How to Properly Dispose of Resources In .NET Core
How to Properly Dispose of Resources In .NET Core
Keeping your application running smoothly.
Join the DZone community and get the full member experience.Join For Free
The .NET garbage collector (GC) does an excellent job of cleaning up unused resources and reclaiming memory once it is no longer needed. Unfortunately, it can only determine when a managed resource is no longer needed but not an unmanaged one.
Even if you don’t use unmanaged resources directly or don’t even know what the word “unmanaged” means, many of .NET’s built-in classes use unmanaged resources under the hood such as those around network communication (System.Net), streams and file handling (System.IO), image manipulation (System.Drawing), and cryptography. The full list is here: https://docs.microsoft.com/en-us/dotnet/api/system.idisposable?view=netcore-3.1.
What Happens if You Don’t Properly Dispose of Unmanaged Resources?
Assuming you’re not directly using unmanaged code, the resources will still be cleaned up but:
- It won’t happen immediately, and your application will continue to consume those resources until the GC decides to clean them up in the background (by invoking the Finalizer — see below).
- The GC will incur a performance penalty during the cleanup process.
If you are directly allocating unmanaged resources, then it’s critical to dispose of them yourself, since they will never be cleaned up automatically and will cause memory leaks or insufficient resources, and your systems may eventually crash.
How do we properly dispose of resources in .NET Core, you ask? To answer that, let me introduce you to IDisposable.
What Is IDisposable?
IDisposable is a built-in .NET interface that, according to Microsoft’s documentation:
“Provides a mechanism for releasing unmanaged resources.”
The interface exposes a “Dispose” method, which, when implemented, should clean up any relevant resources.
C# 8 introduced an asynchronous way to dispose of resources through the use of “IAsyncDisposable” and “DisposeAsync.”
If a class implements IDisposable, it’s generally a sign that it uses unmanaged resources either directly or indirectly and needs to be appropriately disposed of when you are done using it. I say “generally” because, as you will see later on, the interface is sometimes used for other purposes.
It’s essential to keep in mind that IDisposable relies on the programmer to call the “Dispose” method as the runtime will not automatically call it. IDisposable is just a pattern to enable us to release resources deterministically when they are no longer needed.
The Proper Way to Implement IDisposable
The recommended practice is to implement the “Dispose” method in an idempotent way, which means that no matter how many times you call the method, it should only clean up once. We do that by checking whether the object was already disposed of before trying to release the resources, as shown below:
If you plan to inherit from your class, then you should make the “Dispose” method virtual, as follows:
A virtual method allows an inherited class a chance to override the function and cleanup resources, like this:
Warning: If you forget to call the base class “Dispose” method, then your resources will not be fully cleaned up.
IDisposable relies on the programmer to correctly call the “Dispose” method when there is no longer a need for the object. You can add a finalizer to your class to ensure resources are automatically cleaned up even if the “Dispose” method is forgotten. The GC will automatically invoke the Finalizer when it determines the class is no longer needed.
A finalizer is implemented with the ~ character followed by the class name, as shown below:
Warning: Using a finalizer has performance implications and will introduce overhead for the garbage collection process, so it’s best to clean up resources yourself using IDisposable. It is only recommended to use one when you directly own any unmanaged resources as a safety net in case “Dispose” is not called directly.
When using a finalizer, the recommended practice is to centralize the dispose logic into an additional function (we called it Cleanup), which can be called from both the Finalizer and the “Dispose” method.
The Dispose function should signal to the garbage collector that it does not need to call the Finalizer since the resources were already cleaned up and therefore avoid the extra overhead to the GC cleanup process.
Managed code should never be cleaned up when “Dispose” is invoked from the Finalizer because it may have already been cleaned up by the GC.
If you plan to inherit from the above class, then you can mark the Cleanup function as virtual:
The inherited class would override the cleanup function as follows:
The Proper Way to Use IDisposable
Rule 1: Dispose of Classes that Implement IDisposable.
The first rule is whenever you are consuming a class that implements the IDisposable interface, you should call the “Dispose” method when you are done with that class.
StreamWriter class, for example. We are going to initialize the class, write a single line of text to a file, and then dispose of it when we are done.
The problem with the above code is that if an exception occurs while we are writing to the file, the StreamWriter will not be disposed of properly.
The proper way to fix it is to wrap things in a try/finally block to ensure “Dispose “will be called, even in the result of an exception.
Rule 2: If Your Class Directly Owns an IDisposable Object, Implement IDisposable
The second rule is that if your class has a member variable, field, or property that implements IDisposable, you should apply IDisposable to give those consuming your class a way to dispose of it.
To “directly own,” something means that other classes are not sharing the object. If it was, then you need to ensure the other classes will not try to access it after it is disposed of by you, and you will not try to access it after it’s disposed of elsewhere.
As an example, the
Logger class below wraps the built-in
StreamWriter class to enable writing to a log file.
StreamWriter class implements IDisposable, we should implement IDisposable in our
Logger class to clean up the StreamWriter’s resources.
Someone consuming our Logger class can dispose of the resources when they are done as follows:
Rule 3: When Directly Using Unmanaged Resources, Implement IDisposable and a Finalizer.
The third rule is that if your class is using any unmanaged resources directly, make sure to implement IDisposable, which will allow a user to dispose of them when they are done. The GC can clean up managed resources automatically but has no way of knowing when you are done with unmanaged ones.
Rule 4: Avoid Unhandled Exceptions in the Dispose Method
Never throw an unhandled exception in a Dispose method. Since Dispose is supposed to happen in a finally block, any unhandled exceptions will bubble up to your application. If Dispose is called from the Finalizer, the entire application may crash.
The “using” Statement
A using statement is a scoped construct that will automatically call the dispose method when exiting its scope even if an exception occurs within.
Take our previous example:
After implementing a using block, our example now looks like this:
Starting with C# 8, a using block doesn’t need the curly braces or parenthesis so we can simplify our example to this:
You can also initialize multiple variables in a using block provided they are the same type, and you don’t use the var keyword, like this:
Note: A using statement may not be a good fit if the StreamWriter class shouldn’t be scoped to the using block or you want to add a catch statement.
Using Can Be Great for Other Purposes
A purist would tell you that the only goal of IDisposable and a using block is to clean up resources, and technically they are correct. The truth is, though, you can do a lot of creative things with a using statement outside of just strictly cleaning up resources.
Microsoft’s documentation hints at this idea by saying.
“There are additional reasons for implementing Dispose, such as undoing something that was previously done. For example, freeing memory that was allocated, removing an item from a collection that was added, signaling the release of a lock that was acquired, and so on.”
Here’s a simple example in which IDisposable is used to write the end </HTML> tag without requiring the user to remember to close it themselves. Remember that the end HTML tag will be written even if an exception occurs within the block.
And the using statement that consumes it looks as follows:
Another example is Microsoft’s class, “TransactionScope.” This class uses IDisposable and a using block to manage database transactions.
Properly disposing of resources is key to keeping your .NET applications running smoothly. For those of us not using unmanaged code directly, it boils down to ensuring the “Dispose” method is called on any IDisposable class you consume, whether it’s called directly or indirectly through a using statement. For those of us handling unmanaged code, care must be taken to ensure not only that we are providing a proper way to dispose of it, but also that we are handling when a programmer forgets to dispose of it properly.
I hope you enjoyed this article, feel free to leave any comments.
Opinions expressed by DZone contributors are their own.