flow.
"Flow is a condition of deep, nearly meditative involvement." - Tom DeMarco

Automatic Preprocessing and Postprocessing via Anonymous Delegates

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!

kick it on DotNetKicks.com

Feedback

# re: Automatic Preprocessing and Postprocessing via Anonymous Delegates

That is fantastic! you have got me thinking about how this could be used with unitofwork....a friend of mine actually wrote the exact same thing for a project at work, but i couldnt understand it, this is really really cool, nice work! 4/30/2007 10:47 AM | Chris Davis

# re: Automatic Preprocessing and Postprocessing via Anonymous Delegates

Awesome! I'm glad it got the gears turning. Don't thank me though, thank Venkat :)

(I'm really psyched it helped though :) 4/30/2007 11:02 AM | Jeremy

# re: Automatic Preprocessing and Postprocessing via Anonymous Delegates

Out of curiosity, I'm just wondering why the 1st code sample uses abstract mehods whereas the second one doesn't.

And also, couldn't I wrap it in a constructor/destructor as well? Wouldn't that be a better solution?

Hope my questions are not totally out of ignorance and sound slightly constructive :-)

Chris 5/1/2007 6:26 PM | Chris

# re: Automatic Preprocessing and Postprocessing via Anonymous Delegates

Hi Chris,

Actually, those are great questions. In reference, to your first question, I chose to make the methods abstract in the first example in an attempt to illustrate how someone who implemented their own custom database connection could derive from AbstractDatabaseConnection and simply override the methods that were relevant to them. They would have to remember to call OpenConnection() and CloseConnection() but the leg work of managing the connection would actually be handled by the underlying base class (AbstractDatabaseConnection) where they're implemented. They would have to provide their own custom implementations of ReadRecords() and UpdateRecords() however since the AbstractDatabaseConnection was only concerned with managing the connection, not with performing any meaningful operation. Since the compiler would require the abstract methods to be overridden, this could server as a clue to the implementor that they needed to provide their own custom functionality here.

Also, as far as placing the OpenConnection() and CloseConnection() in the constructor and destructor, respectively, that is a great idea. I alluded to that possibility a bit in the middle of the post but probably didn't spend the time on it that I should of. I would even argue that you're actually correct in your guess that this would be a better solution to the problem. In the example, that I gave, I think simply hiding that code in constructors and destructors would actually be a far cleaner implementation then involving the anonymous delegate. The solution above works best in more complicated situations, and my example was...well...a bit contrived :)

Thanks for your questions, I think they've really helped to clear things up and brought up some great points!

Jeremy 5/1/2007 10:33 PM | Jeremy

 re: Automatic Preprocessing and Postprocessing via Anonymous Delegates

Wouldn't it make more sense to just events in a case like this? 5/2/2007 6:05 PM | tim

# re: Automatic Preprocessing and Postprocessing via Anonymous Delegates

Hi Tim,

Events are definitely an option in this case, and they also have the added bonus that they would be an excellent way to promote loose coupling between your components. In this way, consumers could simply decide whether to respond to the event...or to not.

The example above is intended more for situations in which you do not wish for the consumer to be able to handle the pre and post processing themselves. This is why it is preferred over the first example where it is up to the user to call the pre and post processing methods.

That not withstanding, however, the events method is an excellent idea especially due to the loose coupling that it would permit.

Jeremy 5/2/2007 6:46 PM | Jeremy

# re: Automatic Preprocessing and Postprocessing via Anonymous Delegates

I saw this technique originally used at Venkat Suramaniam's blog (see post at http://www.agiledeveloper.com/blog/PermaLink.aspx?guid=d0b63ad1-1cf5-4bab-8adc-d7d03b83529e). He also talked about it on the dnrTV episode regarding Ruby and C# (http://dnrtv.com/default.aspx?showID=57).

It is just another reason that I wish VB.NET had anonymous delegates. *sigh* 5/3/2007 10:51 AM | David Mohundro

# re: Automatic Preprocessing and Postprocessing via Anonymous Delegates

You know, I should really read the post... you already provided those links! (whoops)

Sorry about that.

Anyway, like my other point, though, I really wish VB.NET had this support :-) 5/3/2007 10:52 AM | D

# re: Automatic Preprocessing and Postprocessing via Anonymous Delegates

Thanks Dave! Actually I didn't have the link to his blog post. Now I can update mine to include it :)

I actually didn't realize VB.NET didn't support anonymous delegates (I"m a C# guy). That's good info to know, regardless.

Thanks for the comment!
Jeremy 5/3/2007 11:07 AM | Jeremy

# re: Automatic Preprocessing and Postprocessing via Anonymous Delegates

Nice posting. You might look at using System.Action<> for you delegate. No need to define your own type. 5/4/2007 6:20 AM | Andrew Robinson

# re: Automatic Preprocessing and Postprocessing via Anonymous Delegates

Hi Andrew,

That's an awesome idea! I can't believe I didn't think of it. It would much cleaner...and why define your own type when one already exists? Thanks for the tip!

Jeremy 5/4/2007 8:21 AM | Jeremy

# re: Automatic Preprocessing and Postprocessing via Anonymous Delegates

Jeremy, I was doing a bunch of work with delegates in a DAL and always defined my own. Action<> was such a time saver! And like yourself, I can't believe that I didn't start using it a sooner. 5/4/2007 11:53 AM | Andrew Robinson

# re: Automatic Preprocessing and Postprocessing via Anonymous Delegates

It's funny how obvious these things seem in retrospect isn't it :) 5/4/2007 10:51 PM | Jeremy

Post a comment





 

Please add 6 and 6 and type the answer here: