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 FreeThe .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.
xxxxxxxxxx
public class MyClass : IDisposable
{
public void Dispose()
{
// Clean up resources here
}
}
C# 8 introduced an asynchronous way to dispose of resources through the use of “IAsyncDisposable” and “DisposeAsync.”
xxxxxxxxxx
using System.Threading.Tasks;
public class MyClass : IAsyncDisposable
{
public async ValueTask DisposeAsync()
{
// Clean up resources here
}
}
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:
xxxxxxxxxx
public class MyClass : IDisposable
{
private bool isDisposed = false;
public void Dispose()
{
if(this.isDisposed)
return;
//Dispose Managed Resources
//Dispose Unmanaged resources
isDisposed = true;
}
}
If you plan to inherit from your class, then you should make the “Dispose” method virtual, as follows:
xxxxxxxxxx
public class MyClass : IDisposable
{
private bool disposed = false;
public virtual void Dispose()
{
//...
}
}
A virtual method allows an inherited class a chance to override the function and cleanup resources, like this:
xxxxxxxxxx
public class MyInheritedClass : MyClass
{
public override void Dispose()
{
//Cleanup logic specific to inherited class
base.Dispose(); // call cleanup function on MyClass
}
}
Warning: If you forget to call the base class “Dispose” method, then your resources will not be fully cleaned up.
Finalizers
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:
xxxxxxxxxx
public class MyClass
{
~MyClass()
{
//Clean up resources
}
}
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.
xxxxxxxxxx
public class MyClass : IDisposable
{
private bool disposed = false;
public void Dispose()
{
Cleanup();
}
private void Cleanup()
{
if(this.disposed)
return;
//Dispose Managed Resources
//Dispose Unmanaged resources
disposed = true;
}
~MyClass()
{
Cleanup();
}
}
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.
xxxxxxxxxx
public class MyClass : IDisposable
{
private bool disposed = false;
public void Dispose()
{
Cleanup();
GC.SuppressFinalize(this);
}
private void Cleanup()
{
if(this.disposed)
return;
//Dispose Managed Resources
//Dispose Unmanaged resources
disposed = true;
}
~MyClass()
{
Cleanup();
}
}
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.
xxxxxxxxxx
public class MyClass : IDisposable
{
private bool disposed = false; public void Dispose()
{
Cleanup(false);
GC.SuppressFinalize(this);
}
private void Cleanup(bool calledFromFinalizer)
{
if(this.disposed)
return;
if(!calledFromFinalizer)
{
//Dispose Managed Resources
}
//Dispose Unmanaged resources
disposed = true;
}
~MyClass()
{
Cleanup(true);
}
}
If you plan to inherit from the above class, then you can mark the Cleanup function as virtual:
xxxxxxxxxx
protected virtual void Cleanup(bool calledFromFinalizer)
The inherited class would override the cleanup function as follows:
xxxxxxxxxx
public class MyInheritedClass : MyClass
{
protected override void Cleanup(bool calledFromFinalizer)
{
//Cleanup logic specific to inherited class
base.Cleanup(calledFromFinalizer);
}
}
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.
Take the 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.
xxxxxxxxxx
StreamWriter writer = new StreamWriter("newfile.txt");
writer.WriteLine("Line of Text");
writer.Dispose();
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.
xxxxxxxxxx
StreamWriter writer = new StreamWriter("newfile.txt");
try
{
writer.WriteLine("Line of Text");
}
finally
{
writer.Dispose();
}
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.
xxxxxxxxxx
public class Logger
{
private readonly StreamWriter _streamWriter;
public Logger()
{
_streamWriter = new StreamWriter("logfile.txt");
}
public void WriteToLog(string text)
{
_streamWriter.WriteLine(text);
}
}
Since the StreamWriter
class implements IDisposable, we should implement IDisposable in our Logger
class to clean up the StreamWriter’s resources.
xxxxxxxxxx
public class Logger : IDisposable
{
private readonly StreamWriter _streamWriter; public Logger()
{
_streamWriter = new StreamWriter("logfile.txt");
}
public void WriteToLog(string text)
{
_streamWriter.WriteLine(text);
}
public void Dispose()
{
_streamWriter.Dispose();
}
}
Someone consuming our Logger class can dispose of the resources when they are done as follows:
xxxxxxxxxx
Logger logger = new Logger();try
{
logger.WriteToLog("Log Line");
}
finally
{
logger.Dispose();
}
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.
xxxxxxxxxx
public class UnmanagedClass : IDisposable
{
private IntPtr pointer;
private bool disposed = false;
public UnmanagedClass()
{
pointer = Marshal.AllocHGlobal(1024);
}
public void Dispose()
{
if(disposed)
return;
Marshal.FreeHGlobal(pointer);
pointer = IntPtr.Zero;
GC.SuppressFinalize(this);
disposed = true;
}
~UnmanagedClass()
{
Dispose();
}
}
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.
xxxxxxxxxx
public class Logger : IDisposable
{
private readonly StreamWriter _streamWriter;
public Logger()
{
_streamWriter = new StreamWriter("logfile.txt");
}
public void WriteToLog(string text)
{
_streamWriter.WriteLine(text);
}
public void Dispose()
{
_streamWriter.Dispose();
throw new Exception();
}
}
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:
xxxxxxxxxx
StreamWriter writer = new StreamWriter("newfile.txt");try
{
writer.WriteLine("Line of Text");
}
finally
{
writer.Dispose();
}
After implementing a using block, our example now looks like this:
xxxxxxxxxx
using(StreamWriter writer = new StreamWriter("newfile.txt"))
{
writer.WriteLine("Line of Text");} //Dispose will be automatically called here
Starting with C# 8, a using block doesn’t need the curly braces or parenthesis so we can simplify our example to this:
xxxxxxxxxx
using StreamWriter writer = new StreamWriter("newfile.txt");
writer.WriteLine("Line of Text");
//Dispose will be automatically called here
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:
xxxxxxxxxx
using StreamWriter writer = new StreamWriter("newfile.txt"),
otherWriter = new StreamWriter("otherFile.txt");
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.
xxxxxxxxxx
public class HTMLWriter : IDisposable
{
public void Dispose()
{
// Write HTML end tag
}
}
And the using statement that consumes it looks as follows:
xxxxxxxxxx
using(var HTML = new HTMLWriter())
{
}
//Dispose is automatically called and the end HTML tag is written
Another example is Microsoft’s class, “TransactionScope.” This class uses IDisposable and a using block to manage database transactions.
xxxxxxxxxx
using (var scope = new TransactionScope)
{
//Any database queries executed here will be batched together in a transaction.
}
// Dispose is called causing the transaction to be executed or rolled back
Summary
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.
Comments