Exceptions and Exception Handling
Things you should know
There's a couple of things all .NET programmers should be aware of when dealing
with exceptions:
- Exceptions are expensive!: Throwing an exception interrupts the flow of control
and forces the .NET runtime to navigate the call stack in order to handle the exception.
For that reason you should always check for error conditions explicitly and handle
them at the start of the function rather than allow them to fall through and get
caught later on (this is good practice anyway as it prevents wasted execution time).
- Exceptions are objects too: You can store exceptions as objects and pass references
to them around like you can any other object - this may help in handling and logging
them depending on your setup.
- There's a difference between "throw;" and "throw ex;": Catching an exception you
can't handle should be avoided (instead, let the exception bubble up to the Global
exception handler), but if you do need to rethrow an exception you should be aware
of exactly what happens when you rethrow it. If you rethrow the exception using
the throw; statement then nothing is done to the exception - its stack trace is
preserved and it is simply bubbled up to the next handler. If, however, you rethrow
using the "throw ex;", where ex is the exception in question, then the stack trace
of the exception is rewritten as that of the point where it was rethrown, thus wiping
out valuable debug information about where the exception originated).
The Base Exception Class
The base exception class contains some very useful properties. The first of these
is the "Message" property, that should contain a useful string description of the
error to let a technical user know what has gone wrong (this is not a user friendly
error message). The second is the StackTrace, which is a string containing a list
(stack) of the functions that were in the process of execution at the point the
error occured. This will probably be your first port of call when debugging a problem.
The final property that will probably be useful to you is the InnerException property
- if you can't find any specific details for the error in the main exception, check
the InnerException (and possibly the InnerException's InnerException) for further
information.
Handling Exceptions
You should hopefully already be aware of the basic exception handling "try {} catch
{} finally {}" structure. The try block is the piece of code in which you are allowing
for exceptions to occur. Following the try block you can specify any number of catch
blocks to catch specific types of exception (using the catch (XXXException ex){}
notation). When declaring these blocks you should order them with the most specific
types of exception first and the more general ones later. If you want to handle
multiple types exceptions in the same way from a single try block then you should
declare a function to handle the exception and pass the exception on to that function
as a parameter since there is no way of sharing exception handler blocks between
different exception types.
The finally block is used to clear up resources after the possibility of an exception
occuring. In .NET 2 Microsoft introduced the "using" structure, which can help reduce
the need for finally blocks and generally keeps code more clean. The Using block
takes an object declaration as a parameter (e.g "using (Filestream f = new Filestream("C:/test.txt",
FileAccess.Write) { ... }" ) and calls the dispose method of that object once you
leave that block (even if you leave it by raising an exception). This makes it a
very good way of ensuring that all connections, files and streams are closed once
you are done with them and have a defined period of scope. Don't get this notation
confused with the "using" that is applied to add namespaces at the beginning of
the class - they are two different commands.
Exceptions are "bubbled" up the call stack. That means, if there is no catch block
that catches the exception or one of its parents then the exception will be passed
up the call stack until an exception handler is found.
Prior to .NET 2 it used to be that COM exceptions could only be caught using the
"catch { }" block ("catch (Exception ex) {} didn't work"). This has been changed
in .NET 2 since it was causing and COM exceptions are now wrapped in their own type
inheritting from the System.Exception class.
Throwing Exceptions
If you are throwing your own exceptions it is best practice to define your own exception
appropriate to the issue that is causing the problem. I have seen this practice
overlooked in industry many times in favour of using "ApplicationException" as a
placeholder. This should only ever be done if the exception will be caught directly
from the try block and even then is still not best practice.
You should also be aware that exceptions should only ever be thrown from the namespace
that declared them unless they are declared in the System namespace, so you should not go through the .NET Framework looking for
appropriate exceptions to throw unless they live in System. If no exceptions are appropriate from the System namespace, you should declare your own exception heirarchy
appropriate to the namespace you are operating in. If you are defining your own
exceptions remember that you should implement an appropriate chain of inheritance
since this is vital for accurate exception handling. You declare inheritance on
an exception the same way you would on any other class (e.g public MyChildException
: MyParentException {} ).
Once you have an exception type that is appropriate all you need to do is call the
constructor and then call the "throw ex;" where ex is the exception you want to
throw.