Using, Close, Dispose and Finalize
Summary
At some point in your use of .NET you are going find that garbage collection, or rather, the lack of garbage collection is causing you a problem. The most likely scenario is that you have some unmanaged resource that you are trying to access and you can’t. Fortunately Microsoft provides us with the tools to handle these types of situations. In this article I’ll be discussing Finalization, Dispose and the IDisposable pattern, Close and the C# specific Using statement. I will also touch on some garbage collection terms. If you aren’t familiar with any of the terms used I strongly suggest you get hold of a good book on CLR Internals and gain an understanding. The .NET garbage collector can be your friend or your worst enemy.
Finalize
Finalization in .NET is the process by which the garbage collector [GC] allows objects to clean up any unmanaged resources prior to destroying the object. When the GC determines an object is eligible for garbage collection it will call that objects finalize method. It is up to that method to close any unmanaged resources. If you have an object that is holding unmanaged resources, but don’t provide a finalize method then you will cause a memory leak in your application as resources will be orphaned with no way to free them.
When an object that is finalizable is created a pointer to it is put on the Finalization queue. When the GC runs looking for objects that no longer have any references to them it also checks the Finalization queue. If an object is in the Finalization queue then that object is not garbage collected. Instead the pointer is removed from the Finalization queue and its Finalize method will be called. The actual call is made by a special Finalization thread. On the next GC the object will be eligible for collection. That doesn’t mean that it will be collected. It is possible for a finalizable object to be moved to a later generation, causing it to live even longer.
This means that finalizable objects take longer to allocate and cost significantly more to free. Make sure you really need that finalizer before you create it.
Declaring a Finalize Method
There are two way to declare a finalize method. Unfortunately if you chose the one below you are given a compiler exception that does more harm that good.
Finalizer Example1
public class FinalizeExample
{
private FileStream _fileStream;
private IntPtr _fileHandle;
private IntPtr _invalidPtr = IntPtr.Zero;
public FinalizeExample()
{
_fileStream = File.Create("c:\temp\testfile.txt");
_fileHandle = _fileStream.Handle;
}
public void WriteToStream(byte logByte)
{
_fileStream.WriteByte(logByte);
}
protected override void Finalize()
{
_fileHandle = _invalidPtr;
_fileStream.Close();
}
}
If you compile this the IDE will give you an error message:
Do not override object.Finalize. Instead, provide a destructor.
The Finalizer is NOT a destructor. Ignore the fact that someone at Microsoft is telling you it is. Actually for whatever reason MS chose to call the finalizer a destructor, but it is confusing so don’t think of it that way!
Destructor vs. Finalizer
A destructor is something that deterministically releases resources [e.g. C++ destructor]. A finalizer is NOT deterministic. The finalize method only gets called when GC happens. I’ll say it again…. The finalize method only gets called when GC happens.
Here’s how you declare the finalizer correctly
public class FinalizerExampleOkay
{
private FileStream _fileStream;
private IntPtr _fileHandle;
private IntPtr _invalidPtr = IntPtr.Zero;
public FinalizerExampleOkay()
{
_fileStream = File.Create("c:\temp\testfile.txt");
_fileHandle = _fileStream.Handle;
}
public void WriteToStream(byte logByte)
{
_fileStream.WriteByte(logByte);
}
~FinalizerExampleOkay()
{
_fileHandle = _invalidPtr;
_fileStream.Close();
}
}
It sure looks like a destructor doesn’t it?
Finalizer Summary
So the finalizer is a way for you to ensure that resources are released when an object is garbage collected. It is NOT a deterministic release of those resources.
- The finalize method is not free, rather it is an expensive operation.
- Objects that have finalize methods are promoted to the next generation when the GC determines they are garbage. They are not eligible to be collected until the next GC run.
- Any objects referenced by the object with the finalize method will be promoted along with the object, which means you will be allocating and holding resources longer than necessary.
- Finalization can be called in any order the CLR chooses. This means that you should never access another object that implements a finalizer from your finalizer. If you do the other object could be finalized first leaving you with a null reference.
- Finalize method will be called even if your object’s constructor threw an exception.
More Reading
Dispose via IDisposable
We’ve talked about how to use a Finalize method to clean up your resources. However, the problem with this method is that you can’t call it. It is called by the .NET Framework for you. In order to allow more control over the release of unmanaged resources MS introduced the concept of a Dispose method. They also introduced the IDisposable interface to indicate that a class has a Dispose method available.
Any class that implements IDisposable MUST
· Implement a finalizer. The reason for this is that Dispose must be called directly by some code. The finalizer exists to handle any case where some code failed to call dispose properly.
· Call its base class Dispose method (if one exists)
· Call GC.SupressFinalize in Dispose() to prevent GC from unnecessary finalization steps
· If called more than once it must not throw an exception
· May throw an exception if the resource it was attempting to free was already freed.
IDisposable Example
public class DisposeExample
{
private FileStream _fileStream;
private IntPtr _fileHandle;
private IntPtr _invalidPtr = IntPtr.Zero;
private bool _disposed = false;
public DisposeExample()
{
_fileStream = File.Create("c:\temp\testfile.txt");
_fileHandle = _fileStream.Handle;
}
public void WriteToStream(byte logByte)
{
_fileStream.WriteByte(logByte);
}
/// <summary>
/// Finalizer to ensure our objects resources are
/// freed
/// </summary>
~DisposeExample()
{
Dispose(false);
}
/// <summary>
/// IDisposable interface implimentation to
/// </summary>
public void Dispose()
{
//pass true indicating managed resources can be freed as well e.g. our code called
//dispose instead of the .NET framework
Dispose(true);
//prevent Finalization since we already freed the resources
GC.SuppressFinalize(this);
}
/// <summary>
/// Virtual Dispose method that any subclasses will inherit and
/// override.
/// </summary>
/// <param name="disposing"></param>
protected virtual void Dispose(bool disposing)
{
if(!_disposed)
{
// If disposing equals true, dispose all managed
// and unmanaged resources.
if(disposing)
{
// Dispose managed resources.
_fileStream.Close(); //note we called close because it was available
}
// Release unmanaged resources. If disposing is false,
// only the following code is executed.
_fileHandle = _invalidPtr;
//thread safety is up to the client!
}
_disposed = true;
}
}
Derived Class
A derived class only needs to implement the virtual Dispose method. Like the base class it needs to determine if it can free managed resources (dispose == true) or not. It must also call the base class Dispose to ensure that all resources are freed.
Derived Example
public class DisposeExampleDerived:DisposeExample
{
private bool _disposed = false;
private IntPtr _unmanagedHandle;
private IntPtr _invalidPtr = IntPtr.Zero;
private FileStream fs = new FileStream(@"C:\temp\tempstream.txt", FileMode.Append);
public DisposeExampleDerived(){}
/// <summary>
/// Virtual Dispose method that any subclasses will inherit and
/// override.
/// </summary>
/// <param name="disposing"></param>
protected override void Dispose(bool disposing)
{
if(!_disposed)
{
//notice we use try/finally to ensure base.Dispose gets called
try
{
if(disposing)
{
// Release the managed resources you added in
// this derived class here.
fs.Close(); //close because they provided close instead of dispose
}
// Release the native unmanaged resources you added
// in this derived class here.
_unmanagedHandle = _invalidPtr;
_disposed = true;
}
finally
{
// Call Dispose on your base class.
base.Dispose(disposing);
}
}
}
}
Further Reading
Close
Close is essentially a method of convenience. It was apparently added because someone thought developers are more likely to call Close() than Dispose(). The recommendation is that if it makes more sense to call Close on something [e.g. a stream object] then implement a Close method and have it call Dispose.
I find the use of the Close method to be just added confusion. If you look closely at the IL for various methods that have a Close method you will see that MS apparently wasn’t really sure about it either. The best thing you can do is to follow the “recommendation”, which is having your Close method call Dispose.
Some Confusing Examples
SqlDataReader:Close()
The Close() method on the SqlDataReader calls an InternalClose() method, which does not call Dispose. Note that previously we stated the correct way to do this was to have your close call dispose. To make it even more confusing the Dispose() method actually calls the Close() method so for this object the order is reversed.
SqlDataAdapter()
This class only contains the Dispose(bool disposing) override. So even in the same namespace we see differences.
What do I call
There has been a lot of discussion around what you should call: Close, Dispose or both. If you follow the recommendations from MS about the IDisposable pattern it shouldn’t matter. Close should do exactly the same thing that Dispose does. I don’t call either as I use the Using statement whenever IDisposable is implemented.
Using
C# has a very convenient way to ensure that a method with IDisposable gets its Dispose method called.
The Using statement. According to MSDN the Using Statement defines a scope at which an item will be disposed.
Using Example
public void UsingCalled()
{
using (SqlCommand cmd = new SqlCommand("sp_something", new SqlConnection("string")))
{
//always supply the commandBehavior!
SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
while(reader.Read())
{
//do something
}
} //this will call close/dispose
}
IL Using Statement
.method public hidebysig instance void UsingCalled() cil managed
{
// Code size 53 (0x35)
.maxstack 3
.locals init ([0] class [System.Data]System.Data.SqlClient.SqlCommand cmd,
[1] class [System.Data]System.Data.SqlClient.SqlDataReader reader)
IL_0000: ldstr "sp_something"
IL_0005: ldstr "string"
IL_000a: newobj instance void [System.Data]System.Data.SqlClient.SqlConnection::.ctor(string)
IL_000f: newobj instance void [System.Data]System.Data.SqlClient.SqlCommand::.ctor(string,
class [System.Data]System.Data.SqlClient.SqlConnection)
IL_0014: stloc.0
.try
{
IL_0015: ldloc.0
IL_0016: ldc.i4.s 32
IL_0018: callvirt instance class [System.Data]System.Data.SqlClient.SqlDataReader [System.Data]System.Data.SqlClient.SqlCommand::ExecuteReader(valuetype [System.Data]System.Data.CommandBehavior)
IL_001d: stloc.1
IL_001e: br.s IL_0020
IL_0020: ldloc.1
IL_0021: callvirt instance bool [System.Data]System.Data.SqlClient.SqlDataReader::Read()
IL_0026: brtrue.s IL_0020
IL_0028: leave.s IL_0034
} // end .try
finally
{
IL_002a: ldloc.0
IL_002b: brfalse.s IL_0033
IL_002d: ldloc.0
IL_002e: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0033: endfinally
} // end handler
IL_0034: ret
} // end of method UsingSample::UsingCalled
Notice how in the above IL the Using Statement was converted into a Try…Finally to ensure our object gets its Dispose method called.
Example 2 Call Close
public void CloseCalled()
{
SqlCommand cmd = new SqlCommand("sp_something", new SqlConnection("string"));
SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
while(reader.Read())
{
//do something
}
reader.Close();
}
IL Close
.method public hidebysig instance void CloseCalled() cil managed
{
// Code size 47 (0x2f)
.maxstack 3
.locals init ([0] class [System.Data]System.Data.SqlClient.SqlCommand cmd,
[1] class [System.Data]System.Data.SqlClient.SqlDataReader reader)
IL_0000: ldstr "sp_something"
IL_0005: ldstr "string"
IL_000a: newobj instance void [System.Data]System.Data.SqlClient.SqlConnection::.ctor(string)
IL_000f: newobj instance void [System.Data]System.Data.SqlClient.SqlCommand::.ctor(string,
class [System.Data]System.Data.SqlClient.SqlConnection)
IL_0014: stloc.0
IL_0015: ldloc.0
IL_0016: ldc.i4.s 32
IL_0018: callvirt instance class [System.Data]System.Data.SqlClient.SqlDataReader [System.Data]System.Data.SqlClient.SqlCommand::ExecuteReader(valuetype [System.Data]System.Data.CommandBehavior)
IL_001d: stloc.1
IL_001e: br.s IL_0020
IL_0020: ldloc.1
IL_0021: callvirt instance bool [System.Data]System.Data.SqlClient.SqlDataReader::Read()
IL_0026: brtrue.s IL_0020
IL_0028: ldloc.1
IL_0029: callvirt instance void [System.Data]System.Data.SqlClient.SqlDataReader::Close()
IL_002e: ret
} // end of method UsingSample::CloseCalled
Notice how there is no Try…Finally in this one and we actually call Close not Dispose.
Example 3 Call Nothing
public void NothingCalled()
{
SqlCommand cmd = new SqlCommand("sp_something", new SqlConnection("string"));
SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
while(reader.Read())
{
//do something
}
}
IL Call Nothing
.method public hidebysig instance void NothingCalled() cil managed
{
// Code size 41 (0x29)
.maxstack 3
.locals init ([0] class [System.Data]System.Data.SqlClient.SqlCommand cmd,
[1] class [System.Data]System.Data.SqlClient.SqlDataReader reader)
IL_0000: ldstr "sp_something"
IL_0005: ldstr "string"
IL_000a: newobj instance void [System.Data]System.Data.SqlClient.SqlConnection::.ctor(string)
IL_000f: newobj instance void [System.Data]System.Data.SqlClient.SqlCommand::.ctor(string,
class [System.Data]System.Data.SqlClient.SqlConnection)
IL_0014: stloc.0
IL_0015: ldloc.0
IL_0016: ldc.i4.s 32
IL_0018: callvirt instance class [System.Data]System.Data.SqlClient.SqlDataReader [System.Data]System.Data.SqlClient.SqlCommand::ExecuteReader(valuetype [System.Data]System.Data.CommandBehavior)
IL_001d: stloc.1
IL_001e: br.s IL_0020
IL_0020: ldloc.1
IL_0021: callvirt instance bool [System.Data]System.Data.SqlClient.SqlDataReader::Read()
IL_0026: brtrue.s IL_0020
IL_0028: ret
} // end of method UsingSample::NothingCalled
Notice how we are left to the mercy of the GC with this example. We don’t call close explicitly which means our SqlConnection object won’t close for use via the CommandBehavior.
There are some subtle things to watch out for when you use the Using Statement. You must make sure that you set the scope correctly. If you don’t you could end up with your object being destroyed either well before you are ready for that to happen, or far too late to do you any real good.
There are a lot of details around Finalizers and how the GC works that easily fill a book (in fact a few have been written). I encourage you to take a closer look at the CLR, download Rotor, get an Internals book and most of make sure you understand what your code is really doing.
Tim - i found this an interesting article and it helped with me trying to figure out connectoins and readers - although i'm not all the way yet!!
I posted something at the URL below and if you get 10 mins to have a read i'd appreciate pointers on anything i may be missing.
http://stevenr2.blogspot.com/2006/01/connections-readers-close-and-dispose.html
Very good article, thanks for posting it.
Excellent point! Thanks for showing it!