This is part 5/5 of my Deterministic Finalization and
IDisposable post series.
This is the final example in my series on deterministic finalization in garbage-collected languages and the true motive behind the series:
AutoReleaseComObject. The idea behind
AutoReleaseComObject is simple: it is nothing but a wrapper around a COM object which calls
Dispose() until the COM object’s reference count is 0 and the object is freed. Here’s the implementation:
Why is this class so useful? Well, it has to do with a topic I’ve discussed before: Excel interop. As I insinuate in that post, a problem that users of the Excel object model often encounter is either runaway Excel processes which never quit, or multiple Excel processes when one would suffice. Furthermore, the Excel processes tend to stay around much longer than they have to. For C++, my solution was to either be sure to explicitly call
COleDispatchDriver::ReleaseDispatch() or to use the
COleDispatchDriver::m_bAutoRelease flag on all Excel objects (this is more than just the application: it is any Excel object such as
In C#, you can run into the same problem — basically the Excel process will stay around as long as any Excel COM interop object has a non-zero reference count. While I suspect the .NET Excel interop objects include code in their finalizers to decrement their COM reference counts to zero, which should mean that in the worst case the Excel process will end at the same time your .NET process ends, I think we can and should do better. After all, consider the implications if your .NET process is very long-lived, or if you repeatedly, serially interact with Excel (the system will likely unnecessarily launch many Excel processes).
The solution to these problems is to call
Marshal.ReleaseComObject() on all Excel objects as soon as possible. Once all objects' COM reference count reach zero, the Excel process will terminate. Therefore, I decided to wrap this functionality into the
Unfortunately, this makes using the Excel object model quite a bit more tedious. The casting becomes annoying, but this is easily solvable by writing a series of Excel object wrappers which inherit from
AutoReleaseComObject and provide access to the wrapped object already casted to the appropriate type (I can’t wait for Whidbey’s generics). I called these objects
ExcelWorkbookWrapper, etc. and their implementation and use should be fairly obvious. However, consider what happens if you execute the following code:
Looks fine, doesn’t it? Wrong.
excelAppWrapper.Application.Workbooks is itself an Excel object model object which also must be wrapped in
AutoReleaseComObject in order for our desired behavior to happen. You need to be very careful to catch and wrap all Excel objects or you are back to square one in having near-immortal Excel processes. The above code should properly be written: