Back in the days of C++, exception safety was really a thing of importance. If I remember correctly, Exceptional C++ by Sutter described this in quite a bit of detail.
One thing that might strike people as odd nowadays is that there is no 'finally' in C++. If you add create an object on the stack, it will be 'disposed' automatically when you exit the scope. To put it bluntly, scope is the thing that '{' and '}' will manage for you.
If you're doing C#, exception handling is still important, but on a different level. In all cases, you have to ensure that your overall state remains consistent. So how does this work?
using
is nothing more than a wrapper around try
, catch
and finally
. It basically creates Dispose
in your finally
, thereby ensuring that when you exit the scope (for whatever reason), the object is cleaned up (that is, assuming it implements IDisposable
). Because of this, the easiest way to ensure objects are cleaned up properly, is by:
- Creating an instance,
- Using the instance,
- Disposing the instance
(in this order!)
E.g.:
using (var connection = new Connection()) // create instance
{
// use connection instance here
} // dispose connection instance -> implicitly created by 'using'.
Re-using code
If you don't want to write the code all over again, which is a good thing (f.ex. with the connection strings), you either manually dispose the objects or you can use a simple callback function to keep the flow the same.
private void Execute(Action<Connection> action)
{
using (var connection = new Connection()) // create instance
{
// use connection instance here
action(connection); // callback; this won't break the described mechanism
} // dispose connection instance -> implicitly created by 'using'.
}
Why not using using
properly is bad
So here's an implementation that returns the connection without any consideration for using:
// Buggy connection builder
private Connection CreateConnection()
{
Connection tmp = new Connection();
// do some stuff with tmp, might throw
return tmp;
}
private void Client()
{
using (var connection = CreateConnection())
{
// do something with the connection, assuming we're safe
}
}
This seems all nice and okay, but it is actually incorrect. Can you see?
In the part where it said "do some stuff with tmp", something might go wrong, which causes the method to throw. When this happens, the object is never returned to the Client, leaving an alive Connection
without a reference. Even though you assumed you were safe, you're actually not.
About constructors and exceptions
If you've paid attention, this also means that things can go wrong in constructors. If you create an object there that isn't cleaned up properly if shit hit the fan, you also have a dangling object. For example:
// Buggy connection wrapper
public class ConnectionWrapper : IDisposable
{
public ConnectionWrapper()
{
this.connection = new Connection();
// do some other stuff, some of which might throw
}
private Connection connection;
// ...
// IDisposable stuff.
}
private void Client()
{
using (var connection = new ConnectionWrapper())
{
// do something with the connection, assuming we're safe
}
}
Again, the code in the constructor can throw an exception. In this case the constructor will not return an object to the client, which means IDisposable
won't be called in the finally
of the using
. This doesn't mean IDisposable
is wrong here-- in fact it isn't!
What goes wrong here is that we create an IDisposable
object that isn't returned and isn't cleaned up. We can fix this by adding a catch
that simply re-throws the exception after cleaning up the connection.