Wednesday, October 17, 2007

Exploiting Closures: What I learned from Ruby that made me a better .Net Developer

The .Net framework has had closures (anonymous delegates) for quite a while, but it never occurred to me how I could utilize them until I was exposed to Ruby. Ruby has a beautifully clear and concise syntax for passing a closure to a method, and its libraries make extensive use of them.

The .Net languages I use (c# and Chrome) are statically typed, which introduces some restrictions and syntactic overhead that Ruby doesn't have. But both c# (3.0) and Chrome are fairly good at type inference, so the overhead isn't as high as you might think. Some examples of how my Ruby-inspired appreciation for closures has allowed me to encapsulate common .Net patterns:

Data Access

My usual data access pattern looks something like this:

    1 using (IDataReader rdr = db.ExecuteReader(CommandType.Text, sql))
    3 {
    4     while (rdr.Read())
    5     {
    6         result.Add( new BusinessObject(...) );
    7     }
    8 }
    9 return result;

Execute the query, loop through the result set, and return a collection of business objects. Rinse. Repeat. Not only does this get repetitive, but I have trouble remembering the using statement. It's not uncommon for me to just forget to dispose the reader when I'm done, which means leaving a database connection open in memory until the garbage collector gets around to cleaning up. Enter GetList:

    1 public static List<T> GetList<T>(Database db, string sql, Func<IDataReader, T> create)
    2 {
    3     List<T> list = new List<T>();
    4     using(IDataReader rdr = db.ExecuteReader(CommandType.Text, sql))
    5     {
    6         while(rdr.Read())
    7         {                    
    8             list.Add(create(rdr));
    9         }
   10     }
   11 
   12     return list;
   13 }

GetList
needs a Database object, the sql to work with, and a closure describing how to turn a row in the result set into a business object. It then returns a typesafe collection representing the query. An example use might be to get a list of name/id pairs, and would look something like this:

    1 //Get the results from the database
    2 return DbUtils.GetList<KeyValuePair<int, string>>(db, sql, 
    3   row => new KeyValuePair<int, string>(
    4           DbUtils.AsInt(row.Item["id"]),
    5           DbUtils.AsString(row.Item["name"]))
    6 );

Where AsInt and AsString are utility methods of mine to handle cases with null fields.

This is, by the way, one example of a very handy use for closures: encapsulating resource allocation and disposal so that you, the developer, don't have to remember to do it. Anyplace you acquire a resource (file access, database access, network operations, etc.) is a candidate for a method like this.

Lazy Loading

Another common pattern of mine is lazy loading: a business object may have some properties which are rarely used and expensive to calculate. In that case, the object defers calculation until they are needed:

    1 private List<string> names;
    2 public List<string> Names
    3 {
    4     get
    5     {
    6         if (names != null)
    7         {
    8             names = ExpensiveComputation();
    9         }
   10         return names;
   11     }
   12 }

Using closures, I've extracted this pattern into the library function LazyLoad:

    1 public static void LazyLoad<T>(ref T stateVar, Func<T> generateVal)
    2 {
    3     if ( stateVar == null || stateVar.Equals(default(T)) )
    4     {
    5         stateVar = generateVal();
    6     }
    7 }

Which transforms the Names property into:

    1 private List<string> names;
    2 public List<string> Names
    3 {
    4     get
    5     {
   6         SysUtils.LazyLoad(ref names, 
   7             () => ExpensiveComputation()
   8         );
   9         return names;
   10     }
   11 }

Notice that even though LazyLoad is a generic method, I am calling as if it were a normal method. The type is inferred from the names parameter, so I can leave out the angle brackets. Type inference wasn't smart enough to figure out the type in the GetList case, but it is here.

The jury is still out for me on whether or not LazyLoad is really a good idea. On the one hand, it encapsulates something I do a lot and gives it a name. On the other hand, it isn't actually much shorter than explicitly testing for null and requires passing the private field by reference, which I try to avoid (although in this case I think it's pretty clear what is happening).

Sort By

Finally, an example that was someone else's idea, but which was also inspired by Ruby: an extension method that implements SortBy on List collections:

    1 public static List<TElement> SortBy<TElement, TSortBy>(
    2     this List<TElement> coll,
    3     Converter<TElement, TSortBy> converter,
    4     Comparison<TSortBy> comparison)
    5 {
    6     return coll
    7         .ConvertAll(el => new { Key = converter(el), Value = el })
    8         .Sort((a, b) => comparison(a.Key, b.Key))
    9         .ConvertAll(x => x.Value);
   10 }
   11 ...
   12 //sample use:
   13 List<string> files = ...//Get some files
   14 files.SortBy(x => GetDate(x)); //sort by modified date

Notice how type inference cleans up the syntax. This function is as elegant (I think) as the Ruby equivalent, but is still fully type-safe.

Conclusion

The point of all this is that closures provide powerful, additional ways of encapsulating common patterns in code. They are fairly new to .Net, but we can look to other languages to gain clues about how to use them best.

No comments: