1. RAII (resource acquisition is initialization)
A stupid name for a great idea. In C++, constructors are mirrored by destructors. After some serious internal and external lobbying right before C# was released, MS added the using
statement, providing at least minimal support for this idea, though there is more they could do. But the usefulness of RAII is still not widely grasped. It's sort of true to say it cleans up "unmanaged resources", but think about what that means: anything other than memory. Think of all the places in your code where you modify state, and later want to put it back again.
Simple example - an Undo system. You want to support "batch" undo transactions, in which several actions get bound up into a single one. The application would do this:
undoSystem.BeginTransaction();
// do stuff, add several undo actions to undoSystem
undoSystem.EndTransaction().
The point is, EndTransaction
MUST be called, however we exit the function, to restore the system to the state we found it in. You should at least use try/finally - but why not follow a pattern consistent with the language? Make BeginTransaction
return an object:
public class UndoTransaction : IDisposable
{
public void Dispose()
{
// equivalent to EndTransaction
}
}
Now the application code can just do this:
using (undoSystem.BeginTransaction())
{
// do stuff, add several undo actions to undoSystem
}
Now there is no need to correctly figure out which method is the "ender" for the "beginner" of the state (which in some situations would not be as obvious as in this example). And ask yourself - would it make much sense for UndoTransaction
to have a finalizer as well? Absolutely NOT. Finalizers cannot safely call on to managed objects, and they run in a different thread. The one thing they are useful for (calling an interop API to dispose of a Win32 handle) is now done much more easily by using SafeHandle
.
Unfortunately the internet and older books are riddled with advice about how IDisposable
implies the need for a finalizer. Ignore them. And also not really explaining the implications of "unmanaged resources" - anything about the state of your program can be regarded as an "unmanaged resource". By taking advantage of IDisposable
/using
, you can apply a consistent coding style to deal with states that change in line with the method call stack. Which brings us to...
2. Exception Safety.
There are some operations that do not throw. Assignment cannot be redefined in C#, so:
x = y;
Assuming x and y are fields or variables of the same type, that will never, ever throw (except under truly bizarre circumstances where you can no longer rely on anything working). How reassuring is that?! But also useful. Think of how, often, one of your methods will update the state of the class it belongs to. Sometimes it will modify two or three (or more) private fields.
What if an exception is thrown at some point during this multiple-update of state? What state is your object left in? Will one of the fields be updated, but not the other two? And does the resulting state make any sense? (does it "satisfy the class invariant"?) Or will it later cause your code to get confused and cause further damage?
The solution is to figure out what the changes need to be, before doing anything to update your fields. Then when you have all the answers ready, do the assignments - safe in the knowledge that assignments never throw.
Again, because of GC, C# programmers have been encouraged to think that this is a C++-specific problem. It's true that exception safety (and RAII) are commonly spoken of in terms of deleting memory allocations, but that is just one example (it happens to be very important in C++). The truth is, exception safety is an issue that concerns any program that has non-trivial modifiable state in it, which is most programs.
Another issue with exceptions is that they are just as much a part of the "interface" exposed by a method as are the parameters and the return value. We are encouraged (by some of the people answering this question) to catch specific exceptions instead of just Exception
itself:
try
{
funkyObect.GetFunky();
}
catch (SocketException x)
{
}
How do you know that GetFunky
throws SocketException
? Either documentation, or trial and error. What if the author of that method later changes it so it doesn't use sockets, so it throws something else? Now you're catching the wrong thing. No warning from the compiler.
Compare with this cautionary tale:
IEnumerable<int> sequenceInts = funkyObject.GetInts();
// I found out in the debugger that it's really a list:
List<int> listInts = (List<int>)sequenceInts;
Very clever, until the author of GetInts
changes it to use yield return
instead of returning List<int>
.
The moral is that you shouldn't rely on undocumented, untyped coincidences, you shouldn't sniff out the internals of a method you are calling. You should respect information hiding. But this applies to exceptions as well. If a method allows a huge variety of exceptions to leak out of it, then it has a very, very complicated interface, which its author probably didn't intend for you to be reliant on. It's not really any of your business how a method works internally.
This is all partly the fault of lazy library authors. When writing a nice clean modular library, consider defining your own exception type(s). Make sure that your library's methods ONLY throw your approprate exception types and document this fact. Your library methods' code will look like this:
try
{
// do all kinds of weird stuff with sockets, databases, web services etc.
}
catch (Exception x) // but see note below
{
throw new FunkyException("Something descriptive", x);
}
I call this normalizing the exceptions. Note that by passing x into the constructor of FunkyException
, we cause it to become the InnerException
. This preserves complete stack trace information for logging/debugging purposes. Also note that this contradicts the advice given by several other answers to this question (including the highest rated answer), and also many blog posts on this subject. But there it is; I think those people are dead wrong. Exceptions are part of the visible interface of a method, and it is just as important to control that aspect of the interface as it is to specify the type of the parameters and return values.
And when catching exceptions thrown by a badly written or badly documented method (one that may or may not throw all manner of exception types - who knows?) I would advise that you do NOT catch whatever specific exception types it throws, discovered by trial and error in the debugger. Instead, just catch Exception
- wherever you need to in order to ensure the exception safety of your program's state. That way, you are not becoming dependent on undocumented or coincidental facts about the internals of other modules.
But...
Unfortunately catching (Exception x)
is a really bad idea until CLR 4.0 comes along. Even then, it still won't be ideal, though not as bad as it is today. And yet, it has long been advised by the Exception Handling block of Microsoft's Enterprise Library!
For the details, see:
In short - if you catch all exceptions, you also catch fatal exceptions (ones that you want to cause your program to stop and capture a stack trace or a mini dump). If your program attempts to limp along after such an exception, it is now running in an unknown state and could do all kinds of damage.
Reponses to several comments from P Daddy:
"Your advice to ignore the conventions prescribed to by the majority of the industry, as well as Microsoft themselves..."
But I'm not advising that at all. The official advice on Dispose
/finalizers used to be wrong but has since been corrected, so that now I'm in agreement with the majority opinion (but at the same time this demonstrates that majority opinion can be wrong at any given time). And the technique of wrapping exceptions is widely used by libraries from Microsoft and 3rd parties. The InnerException
property was added for precisely this purpose - why else would it be there?
"IDisposable is not RAII... the using statement, as convenient as it is, is not meant as a generic scope guard..."
And yet it cannot help but be a generic scope guard. Destructors in C++ were not originally intended as a generic scope guard, but merely to allow cleanup of memory to be customised. The more general applicability of RAII was discovered later. Read up on how local instances with destructors are implemented in C++/CLI - they generate basically the same IL as a using
statement. The two things are semantically identical. This is why there is a rich history of solid practise in C++ that is directly applicable to C#, which the community can only benefit from learning about.
"Your Begin/End Transaction model seems to be missing a rollback..."
I used it as an example of some thing with on/off state. Yes, in reality transactional systems usually have two exit routes, so it's a simplified example. Even then, RAII is still cleaner than try
/finally
, because we can make commit require an explicit call but make rollback be the default, ensure that it always happens if there is not a commit:
using (var transaction = undoSystem.BeginTransaction())
{
// perform multiple steps...
// only if we get here without throwing do we commit:
transaction.Commit();
}
The Commit
method stops the rollback from happening on Dispose
. Not having to handle both kinds of exit explicitly means that I remove a bit of noise from my code, and I automatically guarantee from the moment I start the transaction that exactly one of rollback and commit will occur by the time I exit the using
block.
Case Study: Iterators
The IEnumerable<T>
interface inherits IDisposable
. If the implementation needs to do something interesting in its Dispose
method, does that imply that it should also have a finalizer, to protect itself from users who do not call Dispose
?
For an example, look at the most widely used (in modern C#) way of implementing IEnumerable<T>
.
When you write an iterator (a function returning IEnumerable<T>
and utilizing yield return
or yield break
), the compiler writes a class for you which takes care of implementing IEnumerable<T>
. It does do something important in its Dispose
, and yet it does not have a finalizer.
The reason is simple. The Dispose method executes any outstanding finally
blocks in the iterator code. The language implementors realised that it would be better for the finally
block to never run than for it to run on the finalizer thread. This would have required anything called from finally
blocks in iterators to be thread safe!
Fortunately, most clients of IEnumerable<T>
use foreach
, which works exactly like a using
statement - it calls Dispose
for you. But that still leaves the cases where the client needs to directly control the enumeration. They have to remember to call Dispose
. In the event that they don't, a finalizer cannot be assumed to be a safe fallback. So the compiler does not attempt to solve this problem by adding a finalizer.
Ultimately, this is just one (very widely used) example that demonstrates that there is a class of cleanup problems for which lazy cleanup (GC, finalizer thread) is not applicable. This is why using
/IDisposable
was added to the language - to provide a purely deterministic cleanup pattern - and why it is useful in its own right in situations where a finalizer would be the wrong choice.
This is not to say that you must never add a finalizer to something that is disposable, just that finalizers are only appropriate in a subset of cases.