Thursday, 15 May 2008

(Mis)Using IDisposable: beyond resource clean-up

Anybody who has ever done any work with files, databases and so on, in C#, has had it drummed into them that they must be good citizens and always tidy up after themselves. Usually that means using using. But did you know that the using statement has uses that go far beyond resource cleanup?

As you know, using works in tandem with the IDisposable interface. Whenever you write something like this:

using (StreamReader reader = new StreamReader("MyFile.txt"))
{
    // do some stuff with the file
}

the compiler translates it into this:

StreamReader reader = new StreamReader("MyFile.txt");
try
{
    // do some stuff with the file
}
finally
{
    if (reader != null)
    {
        ((IDisposable)reader).Dispose();
    }
}

The effect of this is to ensure that, however dodgy the stuff you're doing with reader, whatever exceptions are thrown, its Dispose method will always be called.

Traditionally, this pattern has been used to ensure that files are closed, database connections are severed, GDI Brushes are washed up, etc. as soon as code has finished with them. But it's not limited to that. Think laterally. What does the using statement actually do? It guarantees that, at the end of the using block the Dispose method will be called on the variable that you are are using. But the Dispose method doesn't need to dispose of things. It can do anything you like!

Suppose for example that you need to change the state of something temporarily, whilst you do a job, but want to make sure that it gets changed back when you've finished. Then the using statement is here to help!

An example from VSTO Programming

For a practical example, consider the Excel object model. If you want to get good performance whilst you change lots of cells about, you have to tell Excel not to bother redoing calculations, or updating the screen for the duration. But when you've finished mucking about with its worksheets, you certainly do want Excel to repaint and recalculate.

Now suppose that you have several methods, each modifying cells, and each wanting to do it efficiently. Each of them could turn off recalculation and screen updating before they start, and back on when they've finished. But then, if those methods start calling each other you'll end up with a disco, as the screen flashes each time updating is turned off then on again. To get round this, each method should capture the current state, then restore that when it has finished. But think how mucky the code in each method would look if you started doing that in-line. Now consider this:

public void Update1()
{
    using (excel.DisableUpdating())
    {
        // change lots of cells, etc.
        Update2();
    }
}
public void Update2()
{
    using (excel.DisableUpdating())
    {
        // change other cells
    }
}

The DisableUpdating method takes care of capturing the current state, turning off repainting and recalculation, and then returns an IDisposable that will restore the original state. So whatever the nesting of method calls, the right thing will always happen.

Here's the extension method that makes this possible:

using Xl = Microsoft.Office.Interop.Excel;
public static class ApplicationExtensions
{
    public static IDisposable DisableUpdating(this Xl.Application application)
    {
        // preserve the current settings
        var originalScreenUpdating = application.ScreenUpdating;
        var originalCalculationMode = application.Calculation;

        // disable the updates
        application.ScreenUpdating = false;
        application.Calculation = Xl.XlCalculation.xlCalculationManual;

        return new DelegateInvokingDisposer(
            () =>
            {
                // when disposed, restore the original settings
                application.ScreenUpdating = originalScreenUpdating;
                application.Calculation = originalCalculationMode;
            }
            );
    }
}

You can see that this makes good use of closures to capture the existing state from Excel so that it is available for use by the Disposer lambda function.

The DelegateInvokingDisposer class looks like this:

public struct DelegateInvokingDisposer: IDisposable
{
    private readonly Action _disposeMethod;

    public DelegateInvokingDisposer(Action disposeMethod)
    {
        if (disposeMethod == null)
        {
            throw new ArgumentNullException("disposeMethod");
        }

        _disposeMethod = disposeMethod;
    }

    public void Dispose()
    {
        _disposeMethod();
    }
}

You can find other examples of this idea around the blogosphere. Ian Griffiths, for example, has used this technique to improve on the C# lock statement, creating a version that will timeout if unable to acquire a lock. You'll find another example if you look at the WPF framework, and the ItemCollection class, which supplies items to ListBoxes and the like. In WinForms, if you had a block of code that updated the Items property, you would top and tail it with calls to ListBox.BeginUpdate and ListBox.EndUpdate so that the ListBox didn't get redrawn for every additional item. ItemCollection does things differently. It has the DeferRefresh method which returns an IDisposable object. You use it like this:

using (listBox.Items.DeferRefresh())
{
    listBox.Items.Add("MyItem");
    // add lots more items
}

I'm sure, once you get used to the idea, you'll be able to find new uses of using for yourself.

4 comments:

Lars Corneliussen said...

Cool stuff. Some articles I wrote on similar issue:

Using the using-statement

Free .Net Ambient Context Pattern Implementation

Sam said...

Lars,

Thanks.

Pretty blog that you have. It's interesting. I was thinking about something similar to the Ambient Context pattern yesterday. I'll have to have a look at your post in more detail.

larscorneliussen said...

I'm glad you like my blog! Just started it a month ago :-)

Let me know what you think when you have had a look at the code, please.

Lars

larscorneliussen said...

By the way, if you like it, kick it :-)

http://www.dotnetkicks.com/csharp/Nice_way_to_store_data_on_a_single_tread_Ambient_Context_Pattern

Post a Comment