Introduction
In this article, we will discuss the often confused topic of Implementing the IDisposable
interface.
We will try to see when should be need to implement the IDisposable
interface and what is the
right way of implementing it.
Background
Often in our application development we need to aquire some resources. These resources coould be
files that we want to use from a clustered storage drive, database connections etc. The important thing
to remember while using these resources is to release these resources once we are done with it.
C++ programmers are very well aware of the the concept of Resource Acquisition is Initialization(RAII)
.
This programing idiom states that of an object want to use some resources then it is his responsibility
to acquire the resource and release it. It should do so in normal conditions and in exceptional/error conditions
too. This idiom is implemented very beautifully using constructors, destructors and exception handling mechanism.
Let us not digress and see how we can have a similar idiom in C# using IDisposable
pattern.
Using the code
Before looking into the details of IDisposable
interface let us see how we typically use the resources, or what
are the best practices to use the resources. We should always acquire the resources assuming two things. One we could
get the resource and use it successfully. secondly there could be some error/exception while acquiring the resource.
So the resource acquisition and usage should always happen inside a try block. The resource release should always be
in the finally block. As this finally block will be executed always. Let us see a small code snippet to understand a little
better.
Using Try-finally to manage resources
TextReader tr = null;
try
{
tr = new StreamReader(@"Files\test.txt");
string s = tr.ReadToEnd();
Console.WriteLine(s);
}
catch (Exception ex)
{
}
finally
{
if (tr != null)
{
tr.Dispose();
}
}
Putting 'using' in place to manage resources
The other way to do the same thing us to use using
, this way we only have to think about the resource acquisition
and the exception handling. the resource release will be done automatically because the resource acquisition is wrapped
inside the using
block. Lets do the same thing we did above putting the using
block in place.
TextReader tr2 = null;
try
{
using (tr2 = new StreamReader(@"Files\test.txt"))
{
string s = tr2.ReadToEnd();
Console.WriteLine(s);
}
}
catch (Exception ex)
{
}
Both the above approaches are essentially the same but the only difference is that in the first approach
we have to release the resource explicitly where as in the second approach the resource release was done
automatically. using
block is the recommended way of doing such things as it will do the clean up even if the programmers forget to do it.
The use of 'using
' block is possible because the TextReader
class in the above example is implementing
IDisposable
pattern.
A note on Finalizers
Finalizers
are like destructors. they will get called whenever the object goes out of scope. We typically don't need to implement Finalizers
but if we are planning to implement IDisposable
pattern and at the same time
we want the resource cleanup to happen when the local object goes out of scope then we will have to have a
Finalizer
implemented in our class.
class SampleClass
{
~SampleClass()
{
}
}
When and why we need to Implement IDisposable
Now we know how the resource acquisition and release should be done ideally. we also know
that the recommended way of doing this is using
statement. Now its time to see why we might need to know more
about implementing the IDisposable
pattern ourselves.
Lets assume that we are writing a class which will be reused all across the project. This class will
acquire some resources. To acquire these resources our class will be needing some managed objects(like in
above example) and some unmanaged stuff too(like using a COM component or having some unsafe code with pointers).
Now since our class is acquiring resources, the responsibility of releasing these resources also lies with the class.
Let us have the class with all the resource aquisition and release logic in place.
class MyResources
{
TextReader tr = null;
public MyResources(string path)
{
Console.WriteLine("Aquiring Managed Resources");
tr = new StreamReader(path);
Console.WriteLine("Aquiring Unmanaged Resources");
}
void ReleaseManagedResources()
{
Console.WriteLine("Releasing Managed Resources");
if (tr != null)
{
tr.Dispose();
}
}
void ReleaseUnmangedResources()
{
Console.WriteLine("Releasing Unmanaged Resources");
}
}
We have the class ready with the resource allocation and deallocation code. We also have the functions
to use the class. Now we want to use this class following the guidelines earlier in this article. i.e.
- Using the object in a
try-finally
block, acquire resources in try block and release them in finally.
- Using the object in a
using
block, the resource release will be done automatically.
- The object when goes out of scope it should get disposed automatically as it was a local variable.
Implementing IDisposable
Now if we need to perform the clean up using while using our object and facilitate all the above mentioned
functionalities we need to implement the IDisposable
pattern. Implementing IDisposable
pattern will force us to have
a Dispose
function.
Secondly if the user want to use the try-finally
approach then also he can call this Dispose function and the
object should release all the resources.
Lastly and most importantly, lets have a Finalizer
that will release the unmanaged resources when the object
goes out of scope. The important thing here is to do this finalize only if the programmer is not using the
'using
' block and not calling the Dispose
explicitly in a finally block.
For now lets create the stubs for these functions.
class MyResources : IDisposable
{
#region IDisposable Members
public void Dispose()
{
}
#endregion
~MyResources()
{
}
}
So to understand the possible scenario how this class might need disposal let us look at the following
possible use cases for our class. we will also see what should be done in each possible scenario.
- The user will not do anything to relase the resource. We have to take care of releasing resource in finalizer.
- The user will use try-finally block. We need to do the clean up and ensure that finalizer will not do it again.
- The user will put a using 'block'. We need to do the clean up and ensure that finalizer will not do it again.
So here is the standard way of doing this Dispose business. It is also known as dispose pattern. Lets see how this should be
done to achieve all we wanted.
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing == true)
{
ReleaseManagedResources();
}
else
{
}
ReleaseUnmangedResources();
}
~MyResources()
{
Dispose(false);
}
So now the complete definition of our class looks like this. This time I have added some print messages
to the methods so that we can see how things work when we use different approaches.
class MyResources : IDisposable
{
TextReader tr = null;
public MyResources(string path)
{
Console.WriteLine("Aquiring Managed Resources");
tr = new StreamReader(path);
Console.WriteLine("Aquiring Unmanaged Resources");
}
void ReleaseManagedResources()
{
Console.WriteLine("Releasing Managed Resources");
if (tr != null)
{
tr.Dispose();
}
}
void ReleaseUnmangedResources()
{
Console.WriteLine("Releasing Unmanaged Resources");
}
public void ShowData()
{
if (tr != null)
{
Console.WriteLine(tr.ReadToEnd() + " /some unmanaged data ");
}
}
public void Dispose()
{
Console.WriteLine("Dispose called from outside");
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
Console.WriteLine("Actual Dispose called with a " + disposing.ToString());
if (disposing == true)
{
ReleaseManagedResources();
}
else
{
}
ReleaseUnmangedResources();
}
~MyResources()
{
Console.WriteLine("Finalizer called");
Dispose(false);
}
}
Let us use this class first with a try-finally
block
MyResources r = null;
try
{
r = new MyResources(@"Files\test.txt");
r.ShowData();
}
finally
{
r.Dispose();
}
The sequence of operations can be understood by these ouptut messages.
Aquiring Managed Resources
Aquiring Unmanaged Resources
This is a test data. / Some unmanaged data.
Dispose called from outside
Actual Dispose called with a True
Releasing Managed Resources
Releasing Unmanaged Resources
Let us use this class putting a using
block in place
MyResources r2 = null;
using (r2 = new MyResources(@"Files\test.txt"))
{
r2.ShowData();
}
The sequence of operations can be understood by these ouptut messages.
Aquiring Managed Resources
Aquiring Unmanaged Resources
This is a test data. / Some unmanaged data.
Dispose called from outside
Actual Dispose called with a True
Releasing Managed Resources
Releasing Unmanaged Resources
Let us use this and leave the resource release to GC. the unmanaged resources should still be
cleaned up when the finalizer gets called.
MyResources r3 = new MyResources(@"Files\test.txt");
r3.ShowData();
The sequence of operations can be understood by these ouptut messages.
Aquiring Managed Resources
Aquiring Unmanaged Resources
This is a test data. / Some unmanaged data.
Finalizer called
Actual Dispose called with a False
Releasing Unmanaged Resources
Points of Interest
Implementing IDisposable
has always been a little confusing for the beginners. I have tried to jot down my
understanding of when to implement this interface and what is the right way if implementing it.
Although this article is written from a beginner's perspective, I hope this article has been informative and helpful to someone.
History
- 02 July 2012: First Version.