Introduction
Writing correct code in the presence of exceptions is no easy task.
Exceptions introduce a control flow that is very different from normal program
flow. And it takes some forethought and careful coding to guarantee the
consistency and predictability of a program in the (not so) unlikely event of
throwing exceptions.
It is no wonder then that most developers write their code with the total
disregard to exception safety. We always assume that we can "fix it" later on,
which most often ends up to be in a bug report from a very unhappy customer
using the released product.
But What is Exception Safety?
In his remarkable book--Exceptional C++--Herb Sutter defines the various levels of
exception safety. While his treatment is focused on C++, I find his definitions
applicable to C#, and indeed to any language that supports the notion of
exceptions. According to his definition, there are three levels of exception
safety:
- Strong Guarantee, in which case an operation failing because of an exception
will not alter the program state. This implies that such operation is treated as
an atomic unit, which implies some form of commit-or-rollback semantics. That
basically means that client code calling a function will perceive no side
effects if that function throws an exception.
- Basic Guarantee, which only guarantees that throwing an exception will not
leak any resources, but side effects may arise still. The effect of leaking
resources isn't a big issue due to garbage collection of managed resources. But
it can be a real problem when dealing with limited resources such as database
connections, sockets, file handles, and the likes.
- No-throw Guarantee, in which case a method guarantees that it will handle
all thrown exceptions locally and it will not raise them to the caller, at the
same time assuring the consistency of the program's state. This type of
exception safety is necessary in certain cases in C++ code, but I haven't found
any compelling reason to use it in C#. Thus the focus of this article will be on
the strong exception safety guarantee.
In both the strong and the basic guarantee, the developer might choose to
wrap thrown exceptions in application-specific ones before propagating them to
the caller. But note that the .Net documentation suggests that it is usually
better to propagate built-in exceptions directly to the caller without wrapping
them.
Exceptions In Action: An Example
Let's assume that you are developing an instant messaging application. Each
user is represented by a User
class. Each user has a list of bodies
that are stored both locally on the client and on the messaging server. The
server database is represented by a ServerDB class that acts as a facade, hiding
the gory details of updating the server.
The code for the ServerDB
class might look like the following:
class ServerDB
{
.
.
.
public static void AddBuddy( User theUser, User buddy )
{
.
.
.
}
.
.
.
}
While the User
class might look like the following
public class User
{
private SetCollection m_buddies;
.
.
.
public void AddBuddy( User buddy )
{
m_buddies.Add( User );
ServerDB.AddBuddy( this, buddy );
}
}
SetCollection.Add
might throw an exception if we attempt to add
a buddy that already existed in the collections, or it might throw an exception
if it was unable to allocate enough memory (unlikely in a garbage-collected
environment, yet still a possibility.) ServerDB.AddBuddy
might
throw an exception for several reasons, such as not being able to connect to the
remote server. Although this seems to be a very remote possibility, do keep in
mind that this is meant to be a trivial example. The example might be more
realistic if we assume that SetCollection
would attempt to persist
its state to an external file. In such a case, the probability of
SetCollection.Add
throwing an exception would increase drastically.
The code snippet above upholds the strong exception safety guarantee if
SetCollection.Add
throws. But if the ServerDB.AddBuddy
throws, the in-memory copy of the buddies list would be inconsistent with the
server.
A better attempt might be something like this:
public void AddBuddy( User buddy )
{
m_buddies.Add( buddy );
try
{
ServerDB.AddBuddy( this, buddy )
}
catch( Exception ex )
{
m_buddies.Remove( buddy );
throw ex;
}
}
This works fine. The code upholds the strong exception safety guarantee at
the cost of increased code size and, most importantly, reduced readability. The
purpose of the code is obscured a little by the exception handling code. I trust
that you could imagine the situation if the method was more complex than this
trivial two-liner.
A Better Solution: The using
Statement
As you recall from your favorite C# textbook, the using
keyword
performs two very different functions based on its context: it is used to import
namespaces into the current compilation unit, and it is also used in conjunction
with the IDisposable
interface to guarantee proper clean up of
resources at the end of a code block.
For example, suppose that we have a class ResourceWrapper
that
implements IDisposable
and wraps a limited resource that must be
closed as soon as possible. Client code using such a class might look like the
following (the example is taken directly from the MSDN library):
class ResourceWrapper
: IDisposable
{
.
.
.
public void Dispose()
{
}
}
class myApp
{
public static void Main()
{
using (ResourceWrapper r1 = new ResourceWrapper())
{
.
.
.
.
r1.DoSomething();
}
}
}
No matter how the code exits the using
block, whether through an
exception or a normal return, ResourceWrapper.Dispose
will always
get called. Such behavior upholds the strong exception gaurantee quite nicely,
and it can be a good starting point to a solution to our problem.
Back to our original example, the using
directive and the
IDisposable
interface combo don't work well for our situation.
Remember that we want some sort of clean up action to take place only in
the presence of exceptions. Also keep in mind that it's unlikely that we would
be able to change the definition of SetCollection
to support
IDisposable
, since doing so would drastically alter the usage
semantics of the class. We could, however, implement a helper class to do the
clean up on behalf of SetCollection
. We'll call this class
BuddyInserter
, and we'll define it as follows:
public class BuddyInserter
: IDisposable
{
private bool m_disposed = false;
private bool m_dismissed = false;
private User m_insertedBuddy = null;
private SetCollection m_theCollection = null;
public BuddyInserter( SetCollection theCollection, User buddy )
{
m_insertedBuddy = buddy;
m_theCollection = theCollection;
}
~BuddyInserter()
{
Dispose( false );
}
public void Dispose()
{
Dispose( true );
GC.SuppressFinalize( this );
}
protected void Dispose( bool disposing )
{
if ( m_disposed == false )
{
if ( disposing )
{
if ( m_dismissed == false )
{
try
{
m_theCollection.Remove( m_insertedBuddy );
}
catch
{
}
}
}
m_disposed = true;
}
}
public void Dismiss()
{
m_dismissed = true;
}
}
You might notice that the implementation of the protected
Dispose
method is a little verbose. But it follows the guidelines
of the MSDN documentation to the letter. The BuddyInserter
class
holds the information necessary for it to rollback the operation if and only if
the Dismiss
method doesn't get called before Dispose
.
In our example, we would use this class in the following way:
public class User
{
.
.
.
public void AddBuddy( User buddy )
{
m_buddies.Add( buddy );
using ( BuddyInserter inserter =
new BuddyInserter( m_buddies, buddy ) )
{
ServerDB.AddBuddy( this, buddy );
inserter.Dimiss();
}
}
}
Now the code above is both simpler and cleaner than the previous attempt. If
an exception is thrown from SetCollection.Add
, the state remains
consistent and the exception propagates to the caller without any side effects.
If an exception is thrown from ServerDB.AddBuddy
, the
Dispose
method of BuddyInserter
will rollback the
collection to a consistent state with the server database.
But still, the above solution is a minor improvement in terms of readability,
and hand crafting a class like BuddyInserter
each and every time
your run into a similar situation is a daunting task that will lead to more code
bloat. It's obvious that this solution is not practical, so the ever pressured
programmer is likely to revert back to the following solution:
public void AddBuddy( User buddy )
{
m_buddies.Add( User );
ServerDB.AddBuddy( this, buddy );
}
And we are back to square one again. But there's still hope yet.
A Practical Solution
Using reflection and two helper classes, we can attain a solution that is
both elegant and practical. We'll start by defining a simple collection of
IDisposable
objects:
public class ScopeGuard
: IDisposable
{
private bool m_disposed = false;
private Stack m_disposables = new Stack();
public ScopeGuard()
{
}
~ScopeGuard()
{
this.Dispose( false );
}
public void Dispose()
{
this.Dispose( true );
GC.SuppressFinalize( this );
}
protected void Dispose( bool disposing )
{
if ( m_disposed == false )
{
if ( disposing == true )
{
while ( m_disposables.Count != 0 )
{
IDisposable disposableObject
= (IDisposable) m_disposables.Pop();
disposableObject.Dispose();
}
}
m_disposed = true;
}
}
public void Add( IDisposable disposableObject )
{
Debug.Assert( disposableObject != null );
m_disposables.Push( disposableObject );
}
}
The ScopeGuard
has only one method of its own; Add
,
which takes an object implementing IDisposable
and stores it into a
stack collection. The Dispose
method simply pops the
IDisposable
objects off the stack and calls their corresponding
Dispose
methods. The use of a stack to store the objects is crucial
here, since we must guarantee that objects are disposed in reverse order of
their addition.
Using this class with the BuddyInserter
class that we defined
earlier greatly enhances the readability of the code:
public void AddBuddy( User buddy )
{
using ( ScopeGuard guard = new ScopeGuard() )
{
m_buddies.Add( buddy );
BuddyInserter inserter = new BuddyInserter( m_buddies, buddy );
guard.Add( inserter );
ServerDB.AddBuddy( this, buddy );
inserter.Dimiss();
}
}
In the above code, exiting the using
block will call
ScopeGuard.Dispose
, which in turn will call
BodyInseter.Dispose
. BodyInseter.Dispose
will either
undo the insertion of the buddy or do nothing at all, depending on whether
BuddyInserter.Dismiss
was called or not.
But still, we would have to write our own BuddyInserter and such classes
whenever we find ourselves in need of commit-or-rollback behavior. It would be
nice if we could somehow write a generic object that would be able of storing a
pointer to a method, an object on which to call that method, and a list of
arguments, and have the object exhibit the same semantics of the
BuddyInserter
class above.
Delegates may come to mind as a possible solution. But you'll soon find them
of little use, since you can't guarantee that the clean up methods you might
need to call will always have the same signature. After some research, the only
practical solution that I could find relies on reflection.
We'll need one more helper class: ObjectGuard
:
public class ObjectGuard
: IDisposable
{
private MethodInfo m_methodInfo = null;
private object m_target = null;
private object[] m_methodArgs = null;
private bool m_disposed = false;
private bool m_dismissed = true;
public static ObjectGuard Make( object obj, string methodName,
object[] methodArgs )
{
Debug.Assert( obj != null );
Debug.Assert( methodArgs != null );
Type objType = obj.GetType();
Type[] argsTypes = new Type[ methodArgs.Length ];
for ( int i = 0; i < methodArgs.Length; ++i )
{
argsTypes[i] = methodArgs[i].GetType();
}
MethodInfo methodInfo = objType.GetMethod( methodName,
argsTypes );
Debug.Assert( methodInfo != null );
return new ObjectGuard( obj, methodInfo, methodArgs );
}
private ObjectGuard( object target, MethodInfo methodInfo,
object[] methodArgs )
{
m_target = target;
m_methodInfo = methodInfo;
m_methodArgs = methodArgs;
m_dismissed = false;
}
~ObjectGuard()
{
Dispose( false );
}
public void Dispose()
{
Dispose( true );
GC.SuppressFinalize( this );
}
protected void Dispose( bool disposing )
{
if ( m_disposed == false )
{
if ( disposing )
{
if ( m_dismissed == false )
{
try
{
m_methodInfo.Invoke( m_target, m_methodArgs );
}
catch
{
}
}
}
m_disposed = true;
}
}
public void Dismiss()
{
m_dismissed = true;
}
}
Note the static factory method ObjectGuard.Make
, which takes a
name of the method as a string, an object to invoke the method on, and a list of
arguments matching the method's signature. This method asserts that all
parameters are correct and then constructs an instance of
ObjectGuard
class, which in implements IDisposable
and
follows the same semantics of the BuddyInserter
class the we have
seen earlier. Using this class and ScopeGuard
, our
AddBuddy
example becomes something like the following:
public void AddBuddy( User buddy )
{
using ( ScopeGuard guard = new ScopeGuard() )
{
m_buddies.Add( buddy );
ObjectGuard inserterGuard
= ObjectGuard.Make( m_buddies, "Remove",
new object[] { buddy } );
guard.Add( inserterGuard );
ServerDB.AddBuddy( this, buddy );
inserterGuard.Dimiss();
}
}
Save using a string to pass the method name, I believe that the above code is
more elegant and more practical than the usual try
,
catch
and finally
triad. Of course, there will be a
performance hit due to the use of reflection. But I believe that when faced with
a trade off between performance and exception safety, one should always favor
the latter. Once you have a stable and correct application, let profiling guide
you to any performance bottlenecks, and then optimize. Remember that (premature)
optimization is evil.
Another Example: Migrating Your Old Exception Handling Code
You must have written something resembling the following code snippet at one
time or another:
public void InsertBuddy( User theUser, User buddy )
{
SqlConnection conn = null;
SqlTransaction trx = null;
try
{
conn = new SqlConnection( ... );
conn.Open();
trx = conn.BeginTransaction();
trx.Commit();
}
catch( Exception ex )
{
trx.Rollback();
throw ex;
}
finally
{
conn.Close();
}
}
For the trained eye, this code is exception-safe. But the intent is obscured
a little by the exception handling code, and the magic of the
finally
block looks--to me at least--very intimidating.
Writing the above code snippet using ScopeGuard
and
ObjectGuard
looks a lot better in terms of both readability and
maintainability:
public void InsertBuddy( User theUser, User buddy )
{
using ( ScopeGuard guard = new ScopeGuard() )
{
SqlConnection conn = new SqlConnection( ... );
conn.Open();
guard.Add( ObjectGuard.Make( conn, "Close", new object[] {} );
SqlTransaction trx = conn.BeginTransaction();
ObjectGuard trxGuard
= ObjectGuard.Make( trx, "Rollback", new object[] {} );
guard.Add( trxGuard );
trx.Commit();
trxGuard.Dismiss();
}
}
Please note that we didn't hold onto a reference to the
ObjectGuard
calling the SqlConnection.Close
method,
since we want the clean up action to be performed regardless of how we exit the
code block.
Conclusion
It is my belief that exception safety is often a neglected topic among
programmers. And my own personal experience has taught me that such negligence
always comes back to bite you someday. But using the solution outlined above,
I'm finding myself doing the right thing and totally enjoying it. I hope you'll
give this approach a try. Your comments and suggestions are greatly
appreciated.
Acknowledgments
The idea of this article came from an article by
Andrei Alexandrescu and Petru Marginean published on the CUJ's experts' forum. The method that the
aforementioned authors present, while done in C++, is far more superior and
elegant than anything I might attempt to write.