Memory management and resource management are two concepts that are easy to confuse when first starting out is software development. Knowing the difference between them is important to write bug-free, maintainable software.
The objective of this article is to discuss the differences between the two concepts and to clear up any confusion.
Memory is a managed resource – in other words, it's under the direct control of the runtime. When an object goes out of scope and is no longer referenced, the garbage collector automatically clears up the memory, eventually. Thus, as developers, we don't really need to worry about explicitly releasing memory.
It is important to note that, although you could manually call the static method GC.Collect() to instruct the garbage collector to deallocate memory, it is generally not advised as it would mostly do more harm than good.
The exact way garbage collection happens is out of the scope of this blog post, but in short: objects in the heap memory go through a marking phase, in which objects that are still referenced get “marked” to not get garbage collected. Then, we move on to the second phase, in which unmarked objects get garbage collected.
On the other hand, resources such as file handles or open network connections are known as unmanaged resources – they run outside the .NET runtime. Such resources are not automatically “managed” by the CLR (Common Language Runtime), so they must be handled by the developer.
The main way to handle such resources in .NET is by implementing the “IDisposable” interface (we’ll get to that further ahead in this article). This way, the consumer of the object knows that it must be explicitly disposed of when it's no longer needed. This is known as deterministic resource management, meaning it allows the user to deterministically deallocate the resource.
As a side note, it is preferable when trying to dispose of an object to use the “using” keyword, to ensure that the “Dispose” method is called even if an exception occurs within the block. Otherwise, wrap the object inside a “try catch” and call Dispose() inside the finally block.
using (var disposableObject = new SomeDisposableObject())
{
// Code that uses disposableObject
}
Instead of:
var disposableObject = new SomeDisposableObject();
try
{
// Code that uses disposableObject
}
catch (Exception ex)
{
}
finally
{
disposableObject.Dispose();
}
The other way resources get freed up is by finalizers (destructors). Finalizers are called in the garbage collection phase when the consumer doesn’t properly dispose of an object that owns unmanaged resources. This is known as non-deterministic resource management, as the user doesn’t exactly know when the resource will be deallocated.
It's important to note that finalizers introduce additional computational burden and should generally be avoided.
The reason why I’ve left out the implementation of the IDisposable pattern until now, is that we needed to know about finalizers.
There are generally two cases:
The implementation is straightforward:
public class MyResource : IDisposable
{
public void Dispose()
{
// Clean up managed resources, call Dispose on member variables..
}
We need to implement a finalizer, but before we get there, it's important to note that the need to explicitly implement a finalizer and directly handle unmanaged resources is relatively rare. Most of the time, we use existing types (like FileStream, HttpClient…) that already handle resource management correctly.
So, unless you are maintaining legacy code that involve dealing with older APIs or work on low-level library where direct resource management is necessary, you will probably only have to deal with the first case.
As for the code for implementing a finalizer:
public class MyResource : IDisposable
{
private bool _disposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// Clean up managed resources, dispose other IDisposable objects here
}
// Clean up unmanaged resources (release native resources, file handles etc)
_disposed = true;
}
}
// Finalizer
~MyResource()
{
Dispose(false);
}
}
A few notes about this:
Note: it’s crucial to follow this pattern to prevent resource leaks and ensure proper cleanup.
Memory management and resource management are two concepts that are easy to confuse when first starting out is software development. Knowing the difference between them is important to write bug-free, maintainable software.
The objective of this article is to discuss the differences between the two concepts and to clear up any confusion.
Memory is a managed resource – in other words, it's under the direct control of the runtime. When an object goes out of scope and is no longer referenced, the garbage collector automatically clears up the memory, eventually. Thus, as developers, we don't really need to worry about explicitly releasing memory.
It is important to note that, although you could manually call the static method GC.Collect() to instruct the garbage collector to deallocate memory, it is generally not advised as it would mostly do more harm than good.
The exact way garbage collection happens is out of the scope of this blog post, but in short: objects in the heap memory go through a marking phase, in which objects that are still referenced get “marked” to not get garbage collected. Then, we move on to the second phase, in which unmarked objects get garbage collected.
On the other hand, resources such as file handles or open network connections are known as unmanaged resources – they run outside the .NET runtime. Such resources are not automatically “managed” by the CLR (Common Language Runtime), so they must be handled by the developer.
The main way to handle such resources in .NET is by implementing the “IDisposable” interface (we’ll get to that further ahead in this article). This way, the consumer of the object knows that it must be explicitly disposed of when it's no longer needed. This is known as deterministic resource management, meaning it allows the user to deterministically deallocate the resource.
As a side note, it is preferable when trying to dispose of an object to use the “using” keyword, to ensure that the “Dispose” method is called even if an exception occurs within the block. Otherwise, wrap the object inside a “try catch” and call Dispose() inside the finally block.
using (var disposableObject = new SomeDisposableObject())
{
// Code that uses disposableObject
}
Instead of:
var disposableObject = new SomeDisposableObject();
try
{
// Code that uses disposableObject
}
catch (Exception ex)
{
}
finally
{
disposableObject.Dispose();
}
The other way resources get freed up is by finalizers (destructors). Finalizers are called in the garbage collection phase when the consumer doesn’t properly dispose of an object that owns unmanaged resources. This is known as non-deterministic resource management, as the user doesn’t exactly know when the resource will be deallocated.
It's important to note that finalizers introduce additional computational burden and should generally be avoided.
The reason why I’ve left out the implementation of the IDisposable pattern until now, is that we needed to know about finalizers.
There are generally two cases:
The implementation is straightforward:
public class MyResource : IDisposable
{
public void Dispose()
{
// Clean up managed resources, call Dispose on member variables..
}
We need to implement a finalizer, but before we get there, it's important to note that the need to explicitly implement a finalizer and directly handle unmanaged resources is relatively rare. Most of the time, we use existing types (like FileStream, HttpClient…) that already handle resource management correctly.
So, unless you are maintaining legacy code that involve dealing with older APIs or work on low-level library where direct resource management is necessary, you will probably only have to deal with the first case.
As for the code for implementing a finalizer:
public class MyResource : IDisposable
{
private bool _disposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// Clean up managed resources, dispose other IDisposable objects here
}
// Clean up unmanaged resources (release native resources, file handles etc)
_disposed = true;
}
}
// Finalizer
~MyResource()
{
Dispose(false);
}
}
A few notes about this:
Note: it’s crucial to follow this pattern to prevent resource leaks and ensure proper cleanup.