« The Dispose Pattern | Main | GetOrAddValue »

February 4, 2008

Another extension method: DisposeAfter

Ed previously blogged about our null-propagating extension method. This extension method is one example of a pattern that I find very interesting:

public static ? InvokeOnSelf<T>(this T self, Func<T, ?> toInvoke)

Another interesting example of this pattern is DisposeAfter:

public static T DisposeAfter<TDisposable, T>(this TDisposable d,

    Func<TDisposable, T> fn) where TDisposable : IDisposable

DisposeAfter is an extension method constrained to types which implement IDisposable. It lets you pass in work to be done before calling Dispose, and returns the result of that work.

Why is this useful?

Let's look at a simple case of reading a row of data from an IDataReader:

int id = 0;

string name = null;

 

using (IDataReader reader = command.ExecuteReader())

{

    if (reader.Read())

    {

        id = reader.GetInt32(0);

        name = reader.GetString(1);

    }

}

 

// do some work with id and name

This is one case where using an anonymous type instance to encapsulate the read data could be useful. Doing so would enable us to know if data was read; we could simply return null when it wasn't. It would also somewhat simplify the method by reducing the number of local variables.

There's a big problem though: if we declare the anonymous type instance inside the using block, its scope will be constrained. But we cannot declare the anonymous type separately from its initialization.

We want to say something like:

var result; // error CS0818: Implicitly-typed local variables must be initialized

 

using (IDataReader reader = command.ExecuteReader())

{

    if (reader.Read())

        result = new { Id = reader.GetInt32(0), Name = reader.GetString(1) };

}

 

// do some work with result

But that won't compile.

Enter DisposeAfter:

var result = command.ExecuteReader().DisposeAfter(r =>

    r.Read() ? new { Id = r.GetInt32(0), Name = r.GetString(1) } : null);

 

// do some work with result

There, that's nice! Since DisposeAfter is a generic method, the return value is strongly typed. And it doesn't matter to the compiler that we're returning an anonymous type instance; it can still infer the type of result.

Looking at the implementation of DisposeAfter we see that all of the passed in work is still done within a using statement, so objects will properly be disposed (even in the face of troublesome exceptions):

public static T DisposeAfter<TDisposable, T>(this TDisposable d,

    Func<TDisposable, T> fn) where TDisposable : IDisposable

{

    using (d)

        return fn(d);

}

Let me know in the comments if you find this method useful. Hopefully you'll also start to think about other circumstances where passing a delegate (or perhaps an expression tree) to an object via an extension method could be useful.

Posted by Jacob at February 4, 2008 9:12 AM

Trackback Pings

TrackBack URL for this entry:
http://blog.logos.com/mt-cgi/mt-tb.cgi/181

Comments

While the code:

var result = command.ExecuteReader().DisposeAfter(r => r.Read() ? new { Id = r.GetInt32(0), Name = r.GetString(1) } : null);

Gives the coder a feeling that they *really* know C#, I think it confuses everyone else who isn't in that same euphoric moment where one can finally use that construct he learned in the one week intensive course.

That's my big problem with delegates; they're a little too cute. I prefer clarity to cuteness.

Posted by: Michael Hedgpeth at February 4, 2008 11:29 AM

Another thing: is the data access example above how you're doing it, or is it just an example? It seems like there can be a little bit better abstraction to your data layer.

In my experience, the performance gains achieved by not boxing never outweigh the architectural benefits achieved by using boxing. As they say, optimize last.

Posted by: Michael Hedgpeth at February 4, 2008 11:31 AM

Michael--

While I'll certainly concede that the lambda with embedded ternary is a little hard on the eyes, a familiarity with delegates/lambda expressions is becoming a requirement for developing in C#.

With C# 3.0 and Linq growing in popularity, more developers are being exposed to higher order functions. There are indisputably some really powerful things you can only accomplish once you decide to start passing functions around.

As far as the example Data Access code, no, our production code looks nothing like the above sample. I authored the naive sample to be easy to understand in isolation. We'll likely share some of our data access abstractions in the future.

Posted by: Jacob at February 4, 2008 11:47 AM

Jacob,

You make some good points. I'm admittedly not developing with C# 3.0 right now, so I may find myself liking it more once I make the change.

Another thing I'm interested is testable the lambda stuff is with unit tests. Have you gotten to that level?

I'm happy to hear you're using better Data Access abstractions. I'm going through a NHibernate book right now and like that paradigm. I found the Applied Domain Driven Design with Nilsson helpful in all of that. The Microsoft stuff doesn't seem enbable a good domain model yet.

Posted by: Michael Hedgpeth at February 4, 2008 2:56 PM

Post a comment




(you may use HTML tags for style)