Monday, April 23, 2007 6:32 PM
Here's a really cool trick I picked up on an
episode of
dnrTV a while back. Let me be clear, I can't take credit for this one...it's courtesy of
Venkat Suramaniam. I just thought it was way to cool not to pass along.
Update 5/3/07: Dave was kind enough to leave a link in the comments to a blog entry where Venkat discusses this same technique. I've moved it up here so that it's easier for everyone to find. Venkat also brings up the excellent point that this is a great way to ensure an object is properly disposed of, which I don't believe I mentioned. Thanks Dave! (And Venkat :)
http://www.agiledeveloper.com/blog/PermaLink.aspx?guid=d0b63ad1-1cf5-4bab-8adc-d7d03b83529e
Ever have several pieces of code that all start the same and end the same, but what goes on in the middle varies from piece to piece? Picture a database transaction. You open the connection, add a record, then close the connection. You open the connection, update the record, then close the connection. You open the connection, remove the record, then close the connection. Rinse. Repeat. All of these transactions start and end the same, but the creme in the Oreo varies every time. There's a lot of room for code reuse here, but organizing it in way to really take advantage of it isn't totally obvious. Here's a first cut at how you might approach it...
namespace ResourceExample
{
public abstract class AbstractDatabaseConnection :
IDisposable
{
public void OpenConnection()
{
Console.WriteLine("Opening connection...");
}
public abstract void ReadRecords();
public abstract void UpdateRecords();
public void CloseConnection()
{
Console.WriteLine("Closing connection...");
}
#region IDisposable Members
public void Dispose()
{
Console.WriteLine("Disposing of the connection...");
}
#endregion
}
public class MyDatabaseConnection : AbstractDatabaseConnection
{
public override void ReadRecords()
{
Console.WriteLine("Reading records from the database...");
}
public override void UpdateRecords()
{
Console.WriteLine("Updating records in the database...");
}
}
class Program
{
static void Main(string[] args)
{
using (MyDatabaseConnection myDatabaseConnection = new MyDatabaseConnection())
{
myDatabaseConnection.OpenConnection();
myDatabaseConnection.ReadRecords();
myDatabaseConnection.UpdateRecords();
myDatabaseConnection.CloseConnection();
}
}
}
}
This works, but it leaves a bit to be desired. Now granted, there are a lot of ways that we could clean this up. Most notably, we could move the OpenConnection() routine to the constructor and the CloseConnection() routine the Dispose() method which would remove the need for the consumer to have to explicitly call them. And that's just for starters. But there's an even more elegant way to do this using my favorite language feature, anonymous delegates...
namespace ResourceExample
{
public delegate void DatabaseConnectionUser(DatabaseConnection databaseConnection);
public class DatabaseConnection : IDisposable
{
public void OpenConnection()
{
Console.WriteLine("Opening connection...");
}
public void ReadRecords()
{
Console.WriteLine("Reading records from the database...");
}
public void UpdateRecords()
{
Console.WriteLine("Updating records in the database...");
}
public void CloseConnection()
{
Console.WriteLine("Closing connection...");
}
#region IDisposable Members
public void Dispose()
{
Console.WriteLine("Disposing of the connection...");
}
#endregion
public static void Use(DatabaseConnectionUser databaseConnectionUser)
{
using (DatabaseConnection databaseConnection = new DatabaseConnection())
{
databaseConnection.OpenConnection();
databaseConnectionUser(databaseConnection);
databaseConnection.CloseConnection();
}
}
}
class Program
{
static void Main(string[] args)
{
DatabaseConnection.Use(delegate(DatabaseConnection databaseConnection)
{
databaseConnection.ReadRecords();
databaseConnection.UpdateRecords();
});
}
}
}
Check out what just happened. We simply added a delegate and made it available through the new Use() method. This method handles the preprocessing and then hands off to our delegate so the user can perform whatever operations he needs to. Once the user is finished, the delegate hands back off to the method in order to handle the postprocessing. All of this is wrapped safely in using a statement to ensure that Dispose() is called at the end. So, in effect, we ensured that both of the preprocessing and the postprocessing are handled automatically as is proper resource cleanup. All of this was done without the addition of a new class, all we did was restructured the original class a bit and added a delegate!