4 min. read

Dispose Pattern

1
2
3
4
5
6
7
8
9
public class UnhandledStream : IDispose
{
private UnhandledContent mContent;

public override void Dispose()
{
mContent.Close();
}
}

The dispose pattern consists of a simple method : Dispose.
It should clean any resource which is unmanaged by the Garbage Collector and why is that?
Any resource which is handled by the GC can be safely discarded - for example an allocated array:

1
2
3
4
5
public void SomeFunc()
{
int[] array = new int[1048576]; // 1 MB of data
}
// Out of scope the array - 1 mb of data - is safely deallocated by the GC.

What is the GC

The Garbage Collector is a mechanism of the C# Virtual Machine (Called Common Language Runtime) which has a responsibility to collect all Managed allocations - classes, arrays, lists, etc…


An array is a type of resource that will be collected by the GC.
There are resources which the GC doesn’t try to assume how to handle:

  • File handles.
  • Raw pointers (Unmanaged).
  • Sockets
  • Threads
  • Processes

These Resources can’t be collected by the GC, therefore it’s up to us to clean them when the time is ready.

1
2
3
4
5
public void SomeFunc()
{
var myFile = File.Open("MyFile",FileMode.Open);
}
// Oops - out of scope and myFile's underlying file handle isn't collected!

Using the Dispose pattern it’s very easy to clean it up:

1
2
3
4
5
6
public void SomeFunc()
{
var myFile = File.Open("MyFile",FileMode.Open);

myFile.Dispose(); // Cleaning! Safe to collect.
}

‘Using’ - The easiest way to dispose

1
2
3
4
5
6
7
public void SomeFunc()
{
// C# 8.0 allows a scoped using like that:
using var myFile = File.Open("MyFile",FileMode.Open);

// File was collected already out of scope of 'using'
}

The using keyword is the best way to handle an unmanaged resource.
Basically it’s a sugar syntax to the try-finally statement:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void SomeFunc()
{
var myFile = File.Open("MyFile", FileMode.Open);
try
{
}
finally
{
if (myFile != null)
{
((IDisposable)myFile).Dispose();
}
}
}

A rule of thumb is - Reduce scope and lifetime of resources.

Finalization

C# allows another step in guaranteeing discarding unmanaged resource - Destructors or Finalizers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MySocket 
{
private Socket mSocket;

public MySocket()
{
mSocket = new Socket();
}

// Finalizer
~MySocket()
{
mSocket.Close();
}
}

What happens to finalizers?

When the GC is trying to collect a finalized class it puts the instance into a finalization queue and a special thread goes and finalizes all the objects.

Because the finalization queue is holding the object - the instance isn’t actually deleted until the thread is activated.

There are issues with finalizers:

  1. Too many finalized objects may create an overhead in the GC process.
  2. Lazy cleaning hold resources for longer causing higher memory consumption.

My advice:

Don’t use destructors unless you know what you do - it can create more harm than good.

Unlike C++ Destructors which are inherently good - in C# they aren’t designed for the best performance.
So be aware.

Happy disposing and Thanks for reading!


Is this helpful? You can show your appreciation here: Buy me a coffee