Thursday, April 5, 2007

Refactoring Garbage Disposal

One of the most common tasks in my data layer is cleaning up my connections, transactions, and data readers. I do it a lot. The established code block for cleaning up a disposable object looks something like this: 

   Public Sub ExecuteUpdate()

Dim connection As SqlConnection
Dim transaction As SqlTransaction
Dim command As SqlCommand

Try
connection = New SqlConnection
transaction = connection.BeginTransaction

command = connection.CreateCommand()
command.CommandType = CommandType.StoredProcedure
command.CommandText = "UpdateSomeData"
command.ExecuteNonQuery()

transaction.Commit()

Catch ex As SqlException
transaction.Rollback()

Finally

If Not command Is Nothing Then
command.Dispose()
End If

If Not transaction Is Nothing Then
transaction.Dispose()
End If

If Not connection Is Nothing Then
connection.Dispose()
End If

End Try

End Sub

If you work with a lot of disposable objects (and I'm guessing that most developers do), you get to do a lot of this kind of stuff. The checks for Nothing (null in C#) are mandatory--if you think that a command or transaction object won't ever be null/nothing, boy, are you in for a surprise. Just wait until you try to invoke Dispose on one of those objects and it's not there.


It didn't take long for me to realize that the Finally block in my data access layer was ripe for refactoring. (The drawback was that much of the initial code was generated by a tool. Sucky part, that. But newer code uses the refactored stuff I'm about to show you.)


I hate repeating myself. I do. I really, really do. So I looked at that code and decided that I needed a class that would help me to safely dispose of objects. I needed a garbage disposal--kind of like your in-sink erator, where you can simply toss vegetables, egg shells, ice cubes, or whatever suits you, and it safely whisks them down the drain.


There were numerous questions. Should it be a separate class, or a base class? A base class implementation is problematic. It means that anyone that wants access to the methods needs to derive from it; I could see lots of classes that would want to use it but I didn't want to insert the class into the inheritance chain.


(Global modules were ruled out right off the bat. Don't even suggest it. Global functions, like global variables, leave a really bad taste in my mouth. Let's not pick hairs about what the compiler does behind my back. Just let it go, okay? Leave me to my AROCCF* ways.)


I settled on a separate class with Shared (C# static) methods. That way, you could access them on demand, as needed from anywhere. After all, the methods needed to maintain no class state. Everything they need is passed in.


The resulting class provides four overloads of a single method: DisposeOf. Two of the overloads are type-safe versions designed to provide specific handling for the SqlDataReader and SqlConnection objects, ensuring that they're properly closed before being disposed of. One overload takes a paramarray of IDisposable objects, iterates over it, and invokes the last DisposeOf overload.


All of the DisposeOf overloads invoke the one basic implementation, which takes an IDisposable parameter. That method simply ensures that its argument isn't null, thereby avoiding a NullReferenceException. If the argument isn't null, it invokes IDisposable.Dispose on it.


The net effect is that the Finally block is reduced to this:

      Finally
Disposer.DisposeOf(command)
Disposer.DisposeOf(transaction)
Disposer.DisposeOf(connection)
End Try

 Or, in the best-case, scenario, to this:

      Finally
Disposer.DisposeOf(command, transaction, connection)
End Try

There is one caveat to using this class to dispose of data access objects: you should always dispose of your transaction and connection last, and always in that order. Then again, I don't think that's due to this class. I think that's just the proper order of disposal. The overloaded version that takes the paramarray disposes of the objects in the order that you pass them.


If you have other IDisposable objects that you frequently use that require special handling, you can easily extend this class to help you out. This class was built for VB .NET 1.1, since we don't have the using keyword. The class makes it easier to clean up objects that should be cleaned up.


The code for this class follows below. You are free to take this code and use it or modify it however you wish. You aren't required to mention me, credit me, or even acknowledge that I exist. However, if it blackens your eye, bloodies your nose, or blows your foot off, remember that I don't exist. :)


—Mike

Option Strict On

Imports
System.Data.SqlClient

' Provides methods to assist in the safe disposal of objects.
Public NotInheritable Class Disposer

Private Sub New()
' Prevents instantiation
End Sub

' Safely disposes of an object. Avoids a
' NullReferenceException.
Public Shared Sub DisposeOf(ByVal item As IDisposable)
If Not item Is Nothing Then
item.Dispose()
End If
End Sub

' Safely disposes of a SqlDataReader. Avoids a
' NullReferenceException. If the reader is open,
' it is first closed.
Public Shared Sub DisposeOf(ByVal item As SqlDataReader)
If Not item Is Nothing Then
If Not item.IsClosed Then
item.Close()
End If
End If
End Sub

' Safely disposes of a SqlConnection object. Avoids a
' NullReferenceException. If the connection is opened,
' it is first closed.
Public Shared Sub DisposeOf(ByVal connection As SqlConnection)
If Not connection Is Nothing Then
If Not connection.State = ConnectionState.Closed Then
connection.Close()
End If
connection.Dispose()
End If
End Sub

' Safely disposes of an array of disposable objects.
Public Shared Sub DisposeOf(ByVal ParamArray items() As IDisposable)
For Each item As IDisposable In items
If TypeOf item Is SqlConnection Then
DisposeOf(DirectCast(item, SqlConnection))
ElseIf TypeOf item Is SqlDataReader Then
DisposeOf(DirectCast(item, SqlDataReader))
Else
DisposeOf(item)
End If
Next
End Sub

End
Class

* AROCCF = Anal Retentive Obsessive Compulsive Control Freak

1 comment:

Anonymous said...

Who knows where to download XRumer 5.0 Palladium?
Help, please. All recommend this program to effectively advertise on the Internet, this is the best program!