There are a few ways of ensuring this. The main help I find is by utilising the "using" keyword. This is applied as such:
using(SqlConnection connection = new SqlConnection(myConnectionString))
{
/* utilise the connection here */
}
This basically translates into:
SqlConnection connection = null;
try
{
connection = new SqlConnection(myConnectionString);
}
finally
{
if(connection != null) connection.Dispose();
}
As such it only works with types that implement IDisposable.
This keyword is massively useful when dealing with GDI objects such as pens and brushes. However there are scenarios where you will want to hold onto resources for a longer period of time than just the current scope of a method. As a rule it's best to avoid this if possible but for example when dealing with SqlCe it's more performant to keep one connection to the db continuously open. Therefore one can't escape this need.
In this scenario you can't use the "using" but you still want to be able to easily reclaim the resources held by the connection.
There are two mechanisms that you can use to get these resources back.
One is via a finaliser. All managed objects that are out of scope are eventually collected by the garbage collector. If you have defined a finaliser then the GC will call this when collecting the object.
public class MyClassThatHoldsResources
{
private Brush myBrush;
// this is a finaliser
~MyClassThatHoldsResources()
{
if(myBrush != null) myBrush.Dispose();
}
}
However the above code is unfortunately crap. The reason is because at finalizing time you cannot guarantee which managed objects have been collected already and which have not. Ergo the "myBrush" in the above example may have already been discarded by the garbage collector. Therefore it isn't best to use a finaliser to collect managed objects, its use is to tidy up unmanaged resources.
Another issue with the finaliser is that it is not deterministic. Lets say for example I have a class that communicates via a serial port. Only one connection to a serial port can be open at one time. Therefore if I have the following class:
class MySerialPortAccessor
{
private SerialPort m_Port;
public MySerialPortAccessor(string port)
{
m_Port = new SerialPort(port);
m_Port.Open();
}
~MySerialPortAccessor()
{
if(m_Port != null) m_Port.Dispose();
}
}
Then if I used the object like this:
public static void Main()
{
Test1();
Test2();
}
private static void Test1()
{
MySerialPortAccessor port = new MySerialPortAccessor("COM1:");
// do stuff
}
private static void Test2()
{
MySerialPortAccessor port = new MySerialPortAccessor("COM1:");
// do stuff
}
I would have a problem. The issue is that the finaliser is not deterministic. That is to say I cannot guarantee when it will run and therefore get round to disposing my serial port object. Therefore when I run test 2 I might find that the port is still open.
While I could call GC.Collect() between Test1() and Test2() which would solve this problem it isn't recommended. If you want to get the best performance out of the collector then let it do its own thing.
Therefore what I really want to do is this:
class MySerialPortAccessor : IDispable
{
private SerialPort m_Port;
public MySerialPortAccessor(string port)
{
m_Port = new SerialPort(port);
m_Port.Open();
}
public void Dispose()
{
if(m_Port != null) m_Port.Dispose();
}
}
And i'll rewrite my test like this:
public static void Main()
{
Test1();
Test2();
}
private static void Test1()
{
using( MySerialPortAccessor port = new MySerialPortAccessor("COM1:"))
{
// do stuff
}
}
private static void Test2()
{
using( MySerialPortAccessor port = new MySerialPortAccessor("COM1:"))
{
// do stuff
}
}
This will now work.
So what of the finaliser? Why use it?
Unmanaged resources and possible implementations that don't call Dispose.
As the writer of a component library that others use; their code may forget to dispose of the object. It's possible that something else might kill the process and hence the .Dispose() would not occur. Due to these scenarios a finaliser should be implemented to clean any unmanaged resource as a "worst case" scenario but Dispose should also tidy these resources so you have your "deterministic clean up" routine.
So in closing, the pattern recommended in the .NET Framework Guidelines book is to implement both as follows:
public void SomeResourceHoggingClass, IDisposable
{
~SomeResourceHoggingClass()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
}
// virtual so a sub class can override it and add its own stuff
//
protected virtual void Dispose(bool deterministicDispose)
{
// we can tidy managed objects
if(deterministicDispose)
{
someManagedObject.Parent.Dispose();
someManagedObject.Dispose();
}
DisposeUnmanagedResources();
// if we've been disposed by .Dispose()
// then we can tell the GC that it doesn't
// need to finalise this object (which saves it some time)
//
GC.SuppressFinalize(this);
}
}