C# is mostly relieved from the stress of exception safety, thanks to garbage collections. But in some cases it must be handled. A common case is factories creating more than one IDisposable
object.
Coming from a C++ background, the notion of exception safety is natural to me. For a C++ developer exception safety is as important as… as… ehhmm… I can’t really find any equivalent in C#. I guess that’s good because exception safety is a pain to deal with. The most common reason to care about exception safety in C++ is memory management and in C# that is a non issue thanks to the garbage collector. For other resources C# offers the using
construct and in 95% of the cases it deals with the messy details of ensuring that the resource is disposed even in the event of an exception.
Unfortunately there are those 5% that isn’t handled by the using
statement. In this post I’ll use a constructor that allocates a precious resource in the form of objects implementing IDisposable
. The principle is the same however for any function that creates a IDisposable
object whose lifetime will exceed the function execution.
The Problem
Let’s get to some code. This is a simple class holding a precious resource. To make sure that the precious resource is disposed as soon as it is no longer used, the class implements IDisposable
(not using the proper full pattern now, to keep the code shorter).
public class NotExceptionSafe : IDisposable { private readonly PreciousResource resource; public NotExceptionSafe() { resource = new PreciousResource(); resource.Open(); } public void Dispose() { resource.Dispose(); } } |
PreciousResource
writes debug output in both the constructor and the Dispose
method so we can see that it is properly disposed of. Let’s run the code and test it and see how the resource is allocated and released.
Allocated precious resource. Caught InvalidOperationException exception (Failed opening!)
Ooopss… it’s only allocated. Then the test runner catches an exception from the Open
method.
The problem is that since the NotExceptionSafe
instance is never fully constructed it won’t be disposed by the using
block (because no object will ever be fully constructed and returned to the using
). If the constructor fails, then we’re on our own to handle it.
A better design could remove the problem entirely; never do anything in a constructor that risks throwing. Instead provide a separate Open
method. But doing that would totally spoil the point I’m trying to get to, so let’s stick with this design for this post.
To recover from the exception in Open
and dispose the resource we won’t get any help from using
statements. We’re on our own to handle it.
An Exception Safe Constructor
To show the full power of the pattern I’ll make a slightly more complex case with two instances of the precious resource.
public class ExceptionSafe : IDisposable { private readonly PreciousResource resource1, resource2; public ExceptionSafe() { PreciousResource tmp1 = null, tmp2 = null; try { tmp1 = new PreciousResource(); tmp1.Open(); tmp2 = new PreciousResource(); tmp2.Open(); // Commit point, no exception risk any more. resource1 = tmp1; tmp1 = null; resource2 = tmp2; tmp2 = null; } finally { if (tmp1 != null) { tmp1.Dispose(); } if (tmp2 != null) { tmp2.Dispose(); } } } public void Dispose() { resource1.Dispose(); resource2.Dispose(); } } |
Now the allocated resource is properly disposed, even though the Open
method still throws an exception.
Allocated precious resource. Disposed precious resource. Caught InvalidOperationException exception (Failed opening!)
The pattern is quite verbose, but not that complicated. The constructor is now effectively split in three parts.
- Do all the constructor work, but don’t touch the members yet. Instead store everything in local temporary variables.
- A commit section that assigns the values to the real fields and sets the temporaries to null. It is important to make sure that there is no code that could trigger an exception in or after the commit section.
- An finally block that disposes any temporary with a non null value. However far the initialization had come – it will properly dispose of any resource that had been allocated unless the commit section has run and set all temporaries to null.
I’ve shown a constructor here, but the same principle applies to any method that creates IDisposable
objects whose lifetime should last past the method.
Complete source for ExceptionSafe
, NotExceptionSafe
and PreciousSource
classes and the test runner used to run the code.