Skip to main content

Review of Effective C# Item 15: Utilize using and try-finally for Resource Clean-up

The original Effective C++ Series from Scott Meyers was a real revelation for C++ programmers. It grouped together many idioms from the wildly diverse and complex language and made them understandable. It identified many of the pitfalls and made them avoidable. It was a must read for every serious C++ programmer.

Since then all the major language seems to have an effective series. You would think this was a good idea, but most languages are not as wildly complex as C++, with fewer idioms and pitfalls. They're still there, but the languages have been designed with the idioms in mind, and the introductory texts teach them, and with a lot of the pitfalls already avoided. Consequently most effective series for modern languages are smaller and contain a lot of patterns as well. For example, Effective Java starts off with the builder pattern. To my mind that belongs in a patterns book and it certainly should not be the first Java idiom described.

I am currently reading Effective C# by Bill Wagner. I've got as far as chapter 18 and so far it is full of good advice, but, in my opinion, is extremely poorly explained. Items 6 and 7 cover equality and GetHashCode. These are complex concepts in predominantly reference based languages, like C#, and after I'd finished reading the items I didn't feel I understood them much better.

Items 12 to 20 cover resource management. This is a real passion of mine, so naturally I'm quite critical of what's written here, as well as what's actually written. Luckily most of what's written is sound, but part of Item 15 gives, in my opinion, some just plain bad advice. The following item, 16, is another exceptionally badly written item, all though the advice is sound, but I'll leave that for another time.

Item 15: Utilize using and try-finally for Resource Clean-up Resource management is probably the biggest Achilles heal of garbage collected languages. As such, it should probably be the subject of the first section of any effective series, but item 15 out of 50 isn't to bad.

How and why resources need to be managed in C# is explained satisfactorily by the item, so I won't go over it again. However I was highly amused by one paragraph: "Luckily for you, the C# language designers knew that explicitly releasing resources would be a common task. They added keywords to the language to make it easy." Surely this is treating a symptom, not solving the problem and they should have found a way to encapsulate resource management within types.

My real issue with this item is what the author describes as an ugly construct. There is an implied using example that uses both a SqlConnection and a SqlCommand:
public void ExecuteCommand(string connString, string commandString)
{
using (var myConnection = new SqlConnection(connString))
{
using(var myCommand = new SqlCommand(commandString, myConnection))
{
myConnection.Open();
myCommand.ExecuteNonQuery();
}
}
}
The author points out that you've effectively written this construct:
public void ExecuteCommand(string connString, string commandString)
{
SqlConnection myConnection = null;
SqlCommand myCommand = null;

try
{
myConnection = new SqlConnection(connString);
try
{
myCommand = new SqlCommand(commandString, myConnection);

myConnection.Open();
myCommand.ExecuteNonQuery();
}
finally
{
if (myCommand != null)
myCommand.Dispose();
}
}
finally
{
if (myConnection != null)
myConnection.Dispose();
}
}
As he finds it ugly, when allocating multiple objects that implement IDispose, he prefers to write his own try/finally blocks:
public void ExecuteCommand(string connString, string commandString)
{
SqlConnection myConnection = null;
SqlCommand myCommand = null;

try
{
myConnection = new SqlConnection(connString);
myCommand = new SqlCommand(commandString, myConnection);

myConnection.Open();
myCommand.ExecuteNonQuery();
}
finally
{
if (myConnection != null)
myConnection.Dispose();

if (myCommand != null)
myCommand.Dispose();
}
}
I have two problems with this. The first is that if the Finally For Each Release pattern, as described by Kevlin Henney in Another Tale of Two Patterns, is correctly implemented, the null checks, which are a terrible code smell and often the cause of bugs if they get forgotten, would be completely unnecessary:
public void ExecuteCommand(string connString, string commandString)
{
var myConnection = new SqlConnection(connString);

try
{
var myCommand = new SqlCommand(commandString, myConnection);

try
{
myConnection.Open();
myCommand.ExecuteNonQuery();
}
finally
{
myCommand.Dispose();
}
}
finally
{
myConnection.Dispose();
}
}
If the nested try blocks are a problem for you, another method can be introduced:
public void ExecuteCommand(string connString, string commandString)
{
var myConnection = new SqlConnection(connString);

try
{
ExecuteCommand(myConnection, commandString);
}
finally
{
myConnection.Dispose();
}
}

private void ExecuteCommand(SqlConnection myConnection, string commandString)
{
var myCommand = new SqlCommand(commandString, myConnection);

try
{
myConnection.Open();
myCommand.ExecuteNonQuery();
}
finally
{
myCommand.Dispose();
}
}
However, the real problem is that you are only effectively implementing this construct. If you stick with the original nested using blocks, the compiler creates the construct for you and you don't see it. Which means that it really doesn't matter how ugly it might be and ditching the using blocks and writing your own construct just creates the ugliness. Maybe the root of the authors aesthetic objection is the nesting. Again, this is easily overcome by introducing another function:
public void ExecuteCommand(string connString, string commandString)
{
using (var myConnection = new SqlConnection(connString))
{
ExecuteCommand(myConnection, commandString);
}
}

private void ExecuteCommand(SqlConnection myConnection, string commandString)
{
using (var myCommand = new SqlCommand(commandString, myConnection))
{
myConnection.Open();
myCommand.ExecuteNonQuery();
}
}
Finally
In conclusion, the final part of the summary advice given in the chapter which states, "Whenever you allocate one disposable object in a method, the using statement is the best way to ensure that the resources you allocate are freed in all cases. When you allocate multiple objects in the same method, create multiple using blocks or write your own single try/finally block." should be ignored in favour of "... When you allocate multiple objects in the same method, create multiple using blocks."

A PDF version of this review can be found here.

Comments

  1. I know I've directed you to one of my older blog posts on this matter before - on more than one occasion - I feel I should bring it up again since you missed my preferred way to handle the nesting problem with multiple using blocks - which is to just drop the nesting (and outer braces):

    Nuances here:
    http://www.levelofindirection.com/journal/2009/9/24/raii-and-readability-in-c.html

    ReplyDelete
  2. Yes, Steve Love also suggested it and I thought I'd made reference to that. I don't like the clumsy way I feel it looks.

    ReplyDelete
  3. Also, Dispose is allowed to throw, so the hand-rolled try finally is technically, as well as aestetically inferior...

    This is one area where Java actually kind of wins, well, if you count lombok as part of Java:

    @Cleanup Connection connection = foo;
    @Cleanup Command comm = bar;

    Nice and clean :) Doesn't deal with scoping, but hey, your methods are small enough that that doesn't matter, right? ;)

    ReplyDelete

Post a Comment

Popular posts from this blog

Write Your Own Load Balancer: A worked Example

I was out walking with a techie friend of mine I’d not seen for a while and he asked me if I’d written anything recently. I hadn’t, other than an article on data sharing a few months before and I realised I was missing it. Well, not the writing itself, but the end result. In the last few weeks, another friend of mine, John Cricket , has been setting weekly code challenges via linkedin and his new website, https://codingchallenges.fyi/ . They were all quite interesting, but one in particular on writing load balancers appealed, so I thought I’d kill two birds with one stone and write up a worked example. You’ll find my worked example below. The challenge itself is italics and voice is that of John Crickets. The Coding Challenge https://codingchallenges.fyi/challenges/challenge-load-balancer/ Write Your Own Load Balancer This challenge is to build your own application layer load balancer. A load balancer sits in front of a group of servers and routes client requests across all of the serv

Bloodstock 2009

This year was one of the best Bloodstock s ever, which surprised me as the line up didn't look too strong. I haven't come away with a list of bands I want to buy all the albums of, but I did enjoy a lot of the performances. Insomnium[6] sound a lot like Swallow the Sun and Paradise Lost. They put on a very good show. I find a lot of old thrash bands quite boring, but Sodom[5] were quite good. They could have done with a second guitarist and the bass broke in the first song and it seemed to take ages to get it fixed. Saxon[8] gave us some some classic traditional heavy metal. Solid, as expected. The best bit was, following the guitarist standing on a monitor, Biff Bifford ripped off the sign saying "DO NOT STAND" and showed it to the audience. Once their sound was sorted, Arch Enemy[10] stole the show. They turned out not only to be the best band of the day, but of the festival, but then that's what you'd expect from Arch Enemy. Carcass[4] were very disappoin

Catalina-Ant for Tomcat 7

I recently upgraded from Tomcat 6 to Tomcat 7 and all of my Ant deployment scripts stopped working. I eventually worked out why and made the necessary changes, but there doesn’t seem to be a complete description of how to use Catalina-Ant for Tomcat 7 on the web so I thought I'd write one. To start with, make sure Tomcat manager is configured for use by Catalina-Ant. Make sure that manager-script is included in the roles for one of the users in TOMCAT_HOME/conf/tomcat-users.xml . For example: <tomcat-users> <user name="admin" password="s3cr£t" roles="manager-gui, manager-script "/> </tomcat-users> Catalina-Ant for Tomcat 6 was encapsulated within a single JAR file. Catalina-Ant for Tomcat 7 requires four JAR files. One from TOMCAT_HOME/bin : tomcat-juli.jar and three from TOMCAT_HOME/lib: catalina-ant.jar tomcat-coyote.jar tomcat-util.jar There are at least three ways of making the JARs available to Ant: Copy the JARs into th