Monday, April 2, 2007

Close()? Dispose()? A .NET programmer needs not these things!

Hogwash.

In reading Tess' MSDN blog this morning, it got me thinking about some things with object disposal and garbage collection. Question 12 on her little pop quiz in particular:

12. Why is it important to close database connections and dispose of objects? Doesn't the GC take care of that for me?


Of course, the first poster already answers it:

12/ It does, in the finalizer thread, which takes a _long_ time to process things. Basically this shove stuff like connection pooling out of the window, not to mention that basically resources are held for much longer.


I've witnessed several occasions where the Close() and Dispose() methods are completely ignored by programmers. Streams are left hanging, web services are left dangling... it's a mess!

Usually Close() isn't so much of a problem. Developers are typically pretty aware when they're opening a file or database connection. Still, you need to be keenly aware of what you're doing when you write something such as this:


public class SqlConnectionMgr
{
public SqlConnection GetDBConnection(string connectionString)
{
return new SqlConnection(connectionString);
}
}


This is overly simplified, but I've seen similar code dozens of times. Now you've just create a connection to the database, but you've thrown it out into the wild to hope that whomever calls your code remembers to close it. If you can trust that person, fine. If not, you may find it better do something like this:


public class SqlConnectionMgr : IDisposable
{
private SqlConnection connection;

public SqlConnection GetDBConnection(string connectionString)
{
if (connection != null && connection.State == ConnectionState.Open)
connection.Close();
connection = new SqlConnection(connectionString);
return connection;
}

public void Dispose()
{
if (connection != null && connection.State == ConnectionState.Open)
connection.Close();
GC.SuppressFinalize(this);
}

~SqlConnectionMgr()
{
Dispose();
}
}


Now you can retain management of the connection in your own class. You have to implement IDisposable now, but that's a more implicit direction to the consumer that something has to be done, and you can take part of the onus of managing resources off the consumer in case they forget.

Some things to remember when implementing IDisposable:
  • Make sure to also implement a finalizer which calls your Dispose() method in case the programmer forgets to dispose of the object.
  • In the Dispose() method, you need to call the garbage collector's SuppressFinalize(object) method. This will ensure that if the object was already disposed, the finalizer is not called needlessly.


(And yes, I know that in changing my example above it no longer is able to pass out multiple connections. I'll leave that as a challenge for the reader.)

IDisposable is an excellent way of managing resource, because, well, that's why it was added to the framework. You need to be keenly aware of whether or not the objects you're consuming implement this interface. (The easiest way to find out if you're in the middle of coding is to look through the object's IntelliSense list for the Dispose() method.)

C# provides an excellent mechanism for working with disposable objects known as the using statement:


using (MyDisposableObject obj = new MyDisposableObject())
{
// do something ...
}


When this code executes, the object is created and neatly disposed of within the scope of the using statement. This basically is just a clean, shorthand way of writing:


MyDisposableObject obj = new MyDisposableObject();
// do something ...
obj.Dispose();


There are a lot of objects in the .NET framework that use resources that need to be disposed, including unmanaged resources that can wreak havoc if left open waiting for the garbage collector to come along. One in particular that I have run into is the Bitmap class. If you don't dispose of a Bitmap when you're done with it, depending on the size of the image you could end up with a huge chunk of the heap sitting around doing nothing, waiting to be removed from memory at some indeterminate point in the future.

While .NET provides a lovely managed platform, be sure that you are still keenly aware of resources that you're using. While it's not as easy to lose pointers and form memory leaks in a .NET application as it is in an unmanaged C++ application, you can still waste plenty of system resources waiting around for the garbage collector to do your cleanup for you.

No comments: