Showing posts with label Excel. Show all posts
Showing posts with label Excel. Show all posts

Wednesday, 17 September 2014

Text templating with … (gasp) … Excel!

Excel has many unlikely uses (did you know that Excel 97 contained a hidden Flight Simulator?). I’ve just reminded myself of another one: fast text templating.

I needed to convert a data structure that looked like this

image

into one that looked like this

image

In other words, I needed to convert from a delimited text string into XML elements.

It took me about 60 seconds in Excel. Here’s how:

  1. I copied the text containing all the keywords and pasted it into a single cell of an Excel spreadsheet
  2. I selected the cell, and clicked on the Text to Columns tool (you find this in the Data tab)image
  3. In the Text to Columns Wizard, I indicated that the data was Delimited, and then entered the appropriate delimiter – the pipe (‘|’) character in this case. When I clicked Finish, Excel split each keyword into its own cell, going across the sheet.image
  4. Next, I needed the keywords arranged vertically instead of horizontally – in rows instead of columns. So I selected all the columns by clicking the first cell, then pressing Ctrl+Shift+[Right Arrow] to select all the way to the last keyword. Ctrl-C copied all the keywords. After clicking in an empty cell, I selected the Paste Special option. In the Paste Special dialog, right at the bottom, you’ll find the Transpose option, which will convert columns of data into rows, and vice versa.image
  5. In the cell next to the first keyword, I typed a formula that would wrap the keyword in the XML boilerplate. To concatenate strings use ‘&’, and to include quotes in a string use a double quote. So the formula I needed was
    ="<Keyword Name=""" & A3 & """ />"
    image
  6. I selected that first cell again, and double-clicked the solid square at the bottom right of the cell’s selection border. This replicated the formula all the way down the page to the last row with something in it.
  7. Did the Ctrl-C/Ctrl-V dance to copy and paste the generated XML into my code file.
  8. Job’s a good ‘un!

Wednesday, 14 July 2010

C# 4 broke my code! The Pitfalls of COM Interop and Extension methods

A couple of weeks back I got the go-ahead from my boss to upgrade to Visual Studio 2010. What with Microsoft’s legendary commitment to backwards compatibility, and the extended beta period for the 2010 release, I wasn’t expecting any problems. The Upgrade Wizard worked its magic without hiccup, the code compiled, everything seemed fine.

Banana Skin - Image: Chris Sharp / FreeDigitalPhotos.netThen I ran our suite of unit tests. 6145 passed. 15 failed.

When I perused the logs of the failing tests they all had this exception in common:

COMException: Type mismatch. (Exception from HRESULT: 0x80020005 (DISP_E_TYPEMISMATCH))

How could that happen? Nothing should have changed: though I was compiling in VS 2010, I was still targeting .Net 3.5. The runtime behaviour should be identical. Yet it looked like COM Interop itself was broken. Surely not?

A footprint in the flowerbed

I dug deeper. In every case, I was calling an extension method on a COM object – an extension method provided by Microsoft’s VSTO Power Tools, no less.

These Power Tools are painkillers that Microsoft made available for courageous souls programming against the Office Object Model using C#. Up until C# 4, calling a method with optional parameters required copious use of Type.Missing – one use for every optional parameter you didn’t wish to specify. Here’s an example (you might want to shield your eyes):

var workbook = application.Workbooks.Add(Missing.Value);
workbook.SaveAs(
    "Test.xls", 
    Missing.Value, 
    Missing.Value, 
    Missing.Value, 
    Missing.Value, 
    Missing.Value, 
    XlSaveAsAccessMode.xlNoChange, 
    Missing.Value, 
    Missing.Value, 
    Missing.Value, 
    Missing.Value, 
    Missing.Value);

The Power Tools library provides extension methods that hide the optional parameters:

using Microsoft.Office.Interop.Excel.Extensions;

var workbook = application.Workbooks.Add();
workbook.SaveAs(
    new WorkbookSaveAsArgs { FileName = "Test.xls"} );

Can you see what the problem is yet? I couldn’t. So I thought I’d try a work-around.

A big part of C# 4 is playing catch-up with VB.Net improving support for COM Interop. The headline feature is that fancy new dynamic keyword that makes it possible to do late-bound COM calls, but the one that’s relevant here is the support for optional and named parameters. The nice thing is that, whereas using the dynamic keyword requires you to target .Net 4.0, optional and named parameters are a compiler feature, so you can make use of them even if you are targeting .Net 3.5.

That meant that I didn’t need Power Tools any more. In C# 4 I can just rewrite my code like this:

var workbook = application.Workbooks.Add();
workbook.SaveAs( Filename: "Test.xls" );

And that worked. COM Interop clearly wasn’t broken. But why was the code involving extension methods failing?

Looking again over the stack traces of the exceptions in my failed tests I noticed something very interesting. The extension methods didn’t appear. The call was going directly from my method into the COM Interop layer. I was running a Debug build, so I knew that the extension method wasn’t being optimised out.

The big reveal

Then light dawned.

Take a look at the signature of the SaveAs method as revealed by Reflector:

void SaveAs(
   [In, Optional, MarshalAs(UnmanagedType.Struct)] object Filename,
    /* 11 other parameters snipped */)

Notice in particular how Filename is typed as object rather than string. This always happens to optional parameters in COM Interop to allow the Type.Missing value to be passed through if you opt out of supplying a real value.

Now when C# 3.0 was compiling my code and deciding what I meant by

workbook.SaveAs(new WorkbookSaveAsArgs { ... } );

it had to choose between a call to the SaveAs instance method with 12 parameters or a call to the SaveAs extension method with a single parameter. The extension method wins hands down.

But the landscape changes in C# 4.0. Now the compiler knows about optional parameters. It’s as if a whole bunch of overloads have been added to the SaveAs method on Workbook with 0 through to 12 parameters. Which method does the compiler pick this time? Remember that it will always go for an instance method over an extension method. Since new WorkbookSaveAsArgs { … } is a perfectly good object the compiler chooses the SaveAs instance method, completely ignoring the extension method.

All seems well until we hit Run and Excel is handed the WorkbookSaveAsArgs instance by COM Interop. Not knowing how to handle it, it spits it back out in the form of a Type Mismatch exception.

Mystery solved.

The moral

So watch out if your code uses extension methods on COM objects and you’re planning on upgrading to VS 2010. Don’t assume that your code works the same as before just because it compiled OK. Unit tests rule!

Update: Microsoft have confirmed that this is indeed a breaking change that they didn’t spot before shipping.

Monday, 12 April 2010

Back from a bug hunt [or, Don’t Call Excel on a Background Thread]

I’m just back from a two-day bug hunt: an exhausting experience, but with a successful outcome that I’d like to share in case anybody else comes across this critter.

Photoxpress_8059464As he was testing our Excel Add-in, our Tester, Rich, noticed some rather odd behaviour. Whenever he used the Scan Tables feature,  then subsequently closed Excel, Excel.exe would refuse to vacate its slot in Task Manager until forcibly evicted.

The feature in question had my grubby fingerprints all over it, so I signed up to fix the bug. But where to start?

There are abundant examples of this kind of behaviour when automating Excel from the outside – usually caused by managed code not releasing its references to the COM objects. But my Add-in is inside Excel: any references it holds will be trashed when Excel is shutdown.

What then? I clearly remembered testing for just this kind of problem a couple of weeks back, and I was sure the application closed cleanly then. This gave me my starting point for a spot of Binary-Search debugging.

The Find

I began by grabbing a version of the code as it was at the time I thought the code was bug-free. Running it confirmed that was indeed the case. Then I pulled down a version that lay about half-way between then and now. That one exhibited all the symptoms of infestation. Hah! So the bug must have slipped in some time between the first version I checked and the half-way version. And thus I proceeded, wading through version after version, at each step slashing in half the list of changes in which the bug must have concealed itself.

After a couple of hours searching I narrowed it down to two versions: version 3320 had the bug, version 3319 was bug free. The check-in comment on version 3320 was rather revealing:

“Moved long-running table-scan operation to a background thread, and introduced progress reporting”

Those two words “background thread” were a smoking gun if ever I saw one, but who pulled the trigger? (I did mention that this bug was wanted for murder, right?). Now to adjust my binary-search-magnifying glass to zoom in, not on versions, but on lines of code.

I started by hacking the method where I launched the BackgroundWorker that did the table-scan so that, once again, it did its stuff in the UI thread. Sure enough, the bug went away, Excel closing cleanly on request.

Then, having restored the BackgroundWorker, I chopped its DoWork method in two, stubbing out one half at a time so that I could see which bunch of code was causing the problem. DoWork called out to a couple of other components, and I subject their methods to the same treatment. As method-call after method-call confirmed its innocence I closed in on the culprits: calls to the Excel Object model – specifically, calls to the Excel Object model made from a background thread.

Excel gave no indication that it had a problem being called in this way. When called on the background thread, it did exactly as when called on the foreground thread, with nary an exception. Seems it preferred to sulk, and protest in a more underhand way.

The Fix

Having found the problem, how was I to fix it?

I didn’t want to move all the work back on to the foreground thread: but clearly Excel wouldn’t listen if I talked to it from any other. Enter SynchronizationContext. SynchronizationContext is a handy little class that wraps up the complexities of executing code in another thread’s context. In the olden days of Windows Forms you’d use Control.BeginInvoke or Control.Invoke to do this kind of thing; these days WPF’s Dispatcher provides the same features. SynchronizationContext abstracts either Control or Dispatcher depending on what kind of message loop you’re running, and gives you two methods, Send and Post: Send executes code synchronously, Post asynchronously.

So all I needed to do was add a SynchronizationContext parameter to my two services which do all the talking to Excel (my IoC container kindly absorbed the consequences of this change – no ripple-down effect here).

Most places where I was calling Excel, I was querying some value. Like this, for example:

var result = (string)_application.Run("MyMacro")

Afterwards my change it looked like this:

var result = Query(() => (string)_application.Run("MyMacro"));

...

private TResult Query<TResult>(Func<TResult> query)
{
    return _synchronizationContext.SendQuery(query);
}

SendQuery is an extension method I created for SynchronizationContext that looks like this:

public static SynchronizationContextExtensions
{
    public static TResult SendQuery<TResult>(this SynchronizationContext context, Func<TResult> query)
    {
        var result = default(TResult);
        context.Send(parameter => result = query());
        return result;
    }
}

And that nailed it good and proper.

The Finale

So, Bing, if anybody asks you why their Excel Process doesn’t close after running an Add-in (managed or otherwise), tell them to make sure they are not calling into its object model from a background thread.

Thursday, 21 August 2008

Which .Net Excel IO Component should I use?

Introduction

When I wrote up my post about the ways of interacting with Excel from .Net I listed a number of .Net components that can be used for reading and writing native Excel files; I left you with the hard task of picking one for yourself. Today I'm going to write you a helping hand.

Over the last couple of days I've been doing some bug fixing on a little utility we have that analyses an Excel spreadsheet and creates named ranges for cells that are formatted in particular styles. The biggest bug I wanted to fix was terrible performance. When I first looked at the application a couple of months ago the performance was unbelievable: never mind making a cup of coffee whilst it did its stuff; we could go home and get a good nights kip before it delivered the results. It didn't take long to pin most of the blame for that on Excel COM Interop: each call to a property or method on a COM object has a significant overhead, and we were querying the Style property of each cell in a 120Mb workbook!

Hence my interest in .Net components for loading Excel files: I had a license to Syncfusion XlsIO available, and by switching the code to use that instead of Excel Automation through COM, the time to query cell styles dropped to almost nothing. However, XlsIO has some performance issues of its own (more on these in a moment), and it still could take up to an hour for the whole operation to complete. So I decided to have a look at the other components in the market place.

In the hope that there will be at least one other person on the web who finds this useful, I'm recording my first impressions of several of these Excel IO components. I'm not claiming that these are objective reviews: your kilometreage will almost certainly vary.

Syncfusion XlsIO

I've been using XlsIO on and off for a couple of years. Mostly, it has helped me get the job done; several times it has been the cause of temporary localised baldness. My thoughts:

  • It has an API that covers the vast majority of Excel features
  • The API is mostly consistent with the Excel COM API, though there are several exceptions to catch you out: Workbooks.Add vs Worksheets.Create being one trivial example.
  • There are some highly non-trivial differences from the Excel COM API. For example, the Excel Range object has Rows and Columns properties that return references to collections; the XlsIO Range object has properties with the same names, but each return arrays of Range objects and allocate a new array every time you access the property. This is a huge performance pitfall for the unwary.
  • The Range model is different to Excel's in that in XlsIO, a Range can only contain one Rectangular region, whereas excel allows Ranges to be created that are the union of many rectangular regions.
  • As I hinted in the introduction, there are other performance problems. In my large (120Mb) workbook, deleting a single row could take up 20 seconds. Deleting Named ranges was another costly operation. Excel demonstrates that these operations don't have to take that long.
  • The documentation is sparse, to put it politely, and the class reference often doesn't state more than the obvious. "ICombinedRange: represents a Combined Range" being one typical example. I have however had assurances from Syncfusion that they are working to improve this.

XLSReadWrite

I started my exploration of other components by downloading and installing XLSReadWrite. Then uninstalling it again. Call me a CLR snob, but I didn't like the thought of working with a component that is clearly designed for Delphi. This showed because the API commits two capital crimes: Every type in is prefixed with a T; and most of the namespaces contain just one type. The other point to note about XLSReadWrite is that the "shape" of the API is nothing like Excel's so any code you have using COM Interop would need a lot of reworking to use this component.

ActiveXLS

I'm afraid that CLR snobbishness also put me off ActiveXLS. The ActiveXLS team produce Spreadsheet components for both .Net and Java, and it appears that the .Net version is a straight port of the Java version: it is the paired "get_" and "set_" methods, and the absence of Properties that give the game away. Surely an organisation selling a component "optimised for Visual Studio" (as the home page claims) should make .Net developers feel at home and at least use Pascal casing for methods, rather than camel casing (which everybody knows should only be used for method parameters)?

The other thing that struck me as odd was that in the ActiveXLS forums (which have been active since 2006) there have only been about 200 posts. Is it that they have an intuitive API with superb documentation: or perhaps a very small user-base? Maybe I'm just cynical.

SpreadsheetGear

SpreadsheetGear was the component I finally settled on. This appears to be far and away the most mature (though correspondingly the most expensive) of the components that I looked at. Though I didn't try that part of it, this component also offers Windows Forms controls for editing spreadsheets. My impressions:

  • The Object model is very similar to Excel's, and easy to learn. It didn't take me long to port my code from XlsIO (which is also similar to Excel).
  • One big inconsistency is that properties giving access to cells use a 0-based index system rather than the 1-based system in Excel.
  • The Range model is (as far as I can tell) identical to Excel's. Intersect and Union methods are provided for Ranges and seem to work as I'd expect.
  • There's a surprising omission in the API in the current version (2007): no support for Styles. If you need style support you'll need to get the 2008 version (currently in beta).
  • All the non-public code is obfuscated. This can cause problems when debugging. For example when I was trying to look at the Workbook.Names collection in the Quick Watch window (VS 2008) I expanded one of the Name items in the collection, but was unable to inspect any of its properties. It was only by rewriting the expression in Quick Watch window to include a cast to IName that I could see the property values.
  • SpreadsheetGear do not have any public forums that I could find: the only way to get support is to fill in a form and wait for them to get back to you.
  • Performance of the component was very good. Remember the application that took all night with COM Interop, and up to an hour with XlsIO? It now takes under a minute with SpreadsheetGear.
  • As a bonus, here's the answer to an issue that took me an afternoon to figure out (and has caught me out again since then). When you supply a Range address to an IName (whether by using Names.Add or IName.RefersTo) remember to prefix it with an "=" sign; otherwise the IName.RefersToRange property won't get updated.

Honourable Mentions

  • I tried contacting Independentsoft about Spreadsheet.Net but never received a link to the evaluation download. I would judge by the website that this component isn't going to be as complete or mature as the others.
  • Gembox Software offer a completely free version of their spreadsheet control, limited to 5 worksheets of 150 rows each. Unfortunately they don't seem to offer an evaluation version of the full product, so I wasn't able to try it out on my big workbook. A quick scan of the online help shows that the API is not dissimilar to Excel's, and does follow the .Net framework design guidelines.
  • FarPoint Spread does Excel import and export, but its focus is on providing a spreadsheet-like Grid component, so I didn't look into this any further.
  • Infragistics have Infragistics.Excel but it looks like it can only be purchased as part of one of their suites. From the documentation, it doesn't look as fully featured as either XlsIO or SpreadsheetGear.
  • ComponentOne is another component suite vendor that has lumped an Excel IO component in with their suite. Again, the documentation shows that it has fairly limited capabilities compared with the leaders.
  • Aspose.Cells is a component that appears, by my reading of its documentation, to sit somewhere in the middle of the market, in terms of functionality and price. Aspose is a vendor that sells components for .Net and Java. and gets it right: the API's are "localised" for the framework culture. The .Net API gets Properties and Pascal cased methods, and the Java API keeps its get and set methods.

Monday, 30 June 2008

Reading and Writing Excel files with .Net

document_outIt would be an unusual business application that did not have a requirement to export data in one form or another. Probably the most useful form of report to produce is one that can be opened by Excel, because then everybody and his dog can slice and dice and chart and pivot the data, and you don't have to implement any of those features in your application because Excel does it all.

So what options are there for reading and writing Excel files with .Net?

Plain Text

The easiest option is to use text files. Excel is quite happy to open a straight "txt" file, though it will make you tell it how to chop the lines into columns, and that's not terribly user-friendly. More useful are the CSV (Coma Separated Value) or TSV (Tab Separated Value) formats because these have structure built in. Excel will export its own data in any of these formats.

If you opt for this method, check out FileHelpers, an open-source project (released under the LGPL license) for reading and writing structured files. It lets you work a lot like the XML Serialization feature in the .Net framework. You can mark up classes with attributes, and FileHelpers will write data from your classes to structured files (CSV and TSV included), and will also do the reverse, turning structured files into objects. Nice!

The obvious limitation of plain text files is that you can't format the data or include things like charts. For that you need to work with one of the more sophisticated file types.

Native Excel Files

Native Excel files (the ones with the ".xls" extension) are stored in BIFF format - a binary format that is impenetrable to anybody without the specification. The good news is that, in February 2008, Microsoft released the spec for the Excel format, and other binary file formats used by the Office family. The bad news is that the spec for the Excel format alone weighs in at 349 pages, so good luck implementing that! If you're wondering why it is so complicated, Joel Spolsky (who used to work on the Excel team at Microsoft) has done some explaining.

Fortunately, there are quite a number of companies who saw the complexity of the task as an opportunity, and have created components in .Net to read and write native formats Excel files. Here are a few that I've come across (by no means an exhaustive list):

[Update: I've now written up my first impressions of several of these components]

Using one of these libraries is generally the fastest way of dealing with native excel files (fastest both in terms of performance of the code, and how long you take to write that code!). But there is one other option to consider, if for some reason you can't purchase a library, or you want absolute compatibility with Excel: automating Excel itself using COM Interop. This has the advantage of letting you export files in any of the formats that Excel supports. Suresh Behera has put together a list of Microsoft HowTos to get you started with this technique.

You should be aware that this comes with some limitations. The most obvious is that Excel has to be installed on a computer along with your software, and you have to be careful that the version is compatible with the one you developed with. You also can't use this method to access Excel files on a Server since Excel is a component with a UI (perhaps I should say shouldn't, rather than can't, because some people have made this work). Finally, it's pretty slow if you're dealing with large workbooks because of the overhead the COM imposes on method calls.

Open XML Format Excel files

When Microsoft released Office 2007, they changed the default file format for Word, Excel and Powerpoint to new, XML based formats. They've published the specifications for these under the Office Open XML label - the format for Excel is called SpreadsheetML (you might have heard of the controversy as Microsoft went about to get these formats approved as standards, first by the ECMA, and then by ISO). It's not just Office 2007 that supports these formats; Microsoft have provided an add-in to add support to all versions of Office right back to Office 2000.

An XML-based format. That sounds like it should be easy to write. Right? Well it is much easier than working with the binary formats, but the format has still got to support Excel's huge feature list, so it's never going to be simple. You probably want to start by poking around inside some files in the new format. The main thing you need to know is that they are basically zip files stuffed full of xml files. You can change the extension of a ".docx" file (the new Word file format) or ".xlsx" (the new Excel file format) to ".zip", then open it with your favourite Zip extractor. Some other places you might want to check out:

If you're not up for the challenge of reading and writing the format yourself, many of the libraries I listed above have support for it, to varying degrees. And this time, Microsoft themselves are working on components to read and write the formats. Have a look at the Open XML Format SDK. The first version has already been released with some basic support for the formats; the SDK development roadmap indicates that increased functionality will be available later this year.

Monday, 19 May 2008

DebuggerNonUserCode: Suppressing ignorable exceptions in the debugger

Have you ever written code that made you want to wash your hands (or your keyboard!) the minute you'd finished typing? So dirty it was, that you surreptitiously looked around to see if anyone else had noticed the smell emanating from your instance of Visual Studio. We don't like it, but sometimes it's unavoidable. The clock is ticking; Google mentions no alternatives, so you put a peg on your nose, and plunge on.

It doesn't happen often in the clean, fresh-smelling environment of C#; usually it's when I'm writing VBA code that I feel like putting on the rubber gloves. One case that I do encounter in .Net more often than I would like though, is when Exceptions are thrown in non-exceptional cases, and I have to catch those exceptions to determine what to next.

Take Enum.Parse as an example. It converts a string value to an enum value. More likely than not, the string value will have been entered by the user, so there's a good chance that it's not valid. If it can't be parsed, then an ArgumentException is thrown. So if you want to trap for invalid values, you're forced to do this:

enum MyEnum
{
    Value1,
    Value2,
}

bool TryParse(string value, out MyEnum convertedValue)
{
    try
    {
        convertedValue = (MyEnum)Enum.Parse(typeof(MyEnum), value);
        return true;
    }
    catch (ArgumentException)
    {
        convertedValue = 0;
        return false;
    }
}

Apart from looking nasty, there's another problem that you only notice when you start using the debugger. If you've set the debugger to break whenever an exception is thrown - as I usually do to make sure there are no problems sneaking through overly generic catch handlers - it will break in this method whenever there's invalid input, even though you have the catch statement there and it wouldn't really crash your app. You could configure the debugger to ignore ArgumentException - but then you'll likely miss cases that you really do want to know about.

As of .Net 2.0, there's an easy solution to this problem: DebuggerNonUserCode. This is an attribute that you put against a method to tell the debugger "Nothing to do with me guv'. Ain't my code!". The gullible debugger will believe you, and won't break in that method: using the attribute makes the debugger skip the method altogether, even when you're stepping through code; exceptions that occur, and are then caught within the method won't break into the debugger. It will treat it as if it were a call to a Framework assembly, and should an exception go unhandled, it will be reported one level up the call stack, in the code that called the method. All this is part of the Just-My-Code feature, which Mike Stall gives the low-down on here.

Obviously this is powerful stuff. Make sure you've tested your code well before you apply the attribute -  or you won't be able to debug where it's going wrong (actually there is a way to do it - see the note below). For this reason, I'd suggest using it on the smallest method you can. Also make sure that you are catching very specific exceptions - notice that I'm using ArgumentException in this case, rather than a catch-all Exception.

I've found this especially useful when programming Excel. In the Excel object model, collections give you no API for discovering  if particular members exist. The only way to find out is to make the call, and see if you get an exception. Here's an example of an extension method that checks whether a Worksheet exists, decorated with the DebuggerNonuserCode attribute:

using System.Diagnostics;
using XL = Microsoft.Office.Interop.Excel;

public static class WorkbookExtensions 
{
    [DebuggerNonUserCode]
    public static bool TryGetWorksheet(this XL.Workbook wb, string worksheetName, out XL.Worksheet retrievedWorksheet)
    {
        bool exists = false;
        retrievedWorksheet = null;

        try
        {
            retrievedWorksheet = GetWorksheet(wb, worksheetName);
            exists = retrievedWorksheet != null;
        }
        catch(COMException)
        {
            exists = false;
        }

        return exists;
    }

    [DebuggerNonUserCode]
    public static XL.Worksheet GetWorksheet(this XL.Workbook wb, string worksheetName)
    {
        return wb.Worksheets.get_Item(worksheetName) as XL.Worksheet;
    }
}

Notes:

  1. If ever you need to see everything that's going on in your code, including in the DebuggerNonUserCode methods you can turn off the "Just My Code" feature in Visual Studio by going to Tools->Options, then expanding the Debugging node, and selecting General:

    JustMyCodeOption

  2. Jacob Carpenter has written up a Generic version of the TryParse pattern for enums; Michael Goldobin has actually created a TryParse that avoids the exception altogether - but don't read about that yet, because then you won't need my trick!.
  3. The TryParse pattern is already implemented for parsing other primitive types; int, double, decimal, etc. all have their TryParse methods: go and vote for Enum to get it too. As Peter Richie points out in that Feedback issue I linked to, this forced use of exception handling that Enum.Parse makes you do violates the Framework Design Guidelines. Shame on you, Microsoft ;-).

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.