Exceptions 2: Types and filtering

This is the second article in a series about exceptions:

  1. Basics
  2. Types and filtering
  3. Where to put catch blocks and handle exceptions
  4. Finishing up
You can filter which exceptions a given catch block deals with.
Image credit.

Type hierarchy for exceptions

As I said in the previous article, an exception is an object with a particular type.  Often, in C#, that type is Exception.  However, there are derived types for a more specialised error.  For instance:

  • ArgumentNullException
  • ArgumentOutOfRangeException
  • DivideByZeroException – this is what I should have used in the example in the previous article
  • FileNotFoundException
  • NotSupportedException
  • NullReferenceException

Different exception classes will contain different properties, just as with other classes.  The base Exception class contains quite a lot of useful information, such as the stack trace (see The location of catch blocks) and an error message.  The derived classes might add properties to this – for instance ArgumentNullException also contains the name of the parameter that is null.

In C# at least it’s possible to derive your own exception types.  It’s possible to have too many exception types (that make distinctions that aren’t helpful, leading to confusion, bugs and/or some types not being used) and too few (such that it’s impossible or too hard to separate out kinds of error, although see the when part of Filtering exceptions below).

Finding a catch block

The example I gave earlier (divide and someOtherMethod) is about as simple as can be.  There are two dimensions that make this more complicated, and in C# one of these dimensions has relatively recently been made more complicated.  The dimensions are:

  1. Catch blocks filtering which kinds of exception they handle (this is the one that has become a bit more complicated in C# relatively recently)
  2. The location of catch blocks

Filtering exceptions

If you imagine that your code is talking to a database, there are a few possible errors that could be returned when you send a query to the database.  The different errors will likely need to be dealt with in different ways.  For instance:

  1. The query is invalid, e.g. bad syntax – this query will never work, but different queries might work fine.
  2. The database is corrupt – no query will work until a human sorts out the database, and until then continuing to send queries is likely to make things worse.
  3. The database is temporarily overloaded – this query has timed out, but it’s worth trying again later with this query or another one.

This different response is possible, along these lines (the data types and their methods are all imaginary, but it’s generally valid C#):

void sendQueryToDatabase(Query query, DatabaseConnection dbConnection)
{
   try
   {
      dbConnection.SendQuery(query);
   }
   catch (InvalidQueryException invalidQueryException)
   {
      // log the error, give up on this query
   }
   catch (CorruptDatabaseException corruptDatabaseException)
   {
      // log the error, stop the program gracefully
   }
   catch (TimeoutExpiredException timeoutExpiredException)
   {
      // log the error, set things up for waiting, e.g. via a circuit breaker
   }
   catch (Exception e)
   {
      // deal with other kinds of error
   }
}

When an exception emerges from the try block, it’s matched against the catch blocks in the order they are in the code, i.e. the InvalidQueryException catch first.  The contents of the brackets after each catch act as a variable declaration, and if the exception can be assigned to that variable then that catch block matches.  If a catch block matches then its contents are executed and matching stops.  If there’s no match, then the next catch block is checked.  This means that if the exception is the type specified inside the brackets, or is a type derived from the type inside the brackets, there will be a match.

That’s why the catch block with the type Exception is last.  This is the base type of all exceptions, and so will match all exceptions.  If that catch block were first, then none of the other catch blocks would ever be matched.  It acts as a backstop, to handle errors in a general way, assuming that type-specific error handling has been taken care of by the earlier catch blocks.

C# has extended this filtering, by allowing an optional when (expression-that-evaluates-to-true-or-false) at the end of the catch line.  For instance:

…
catch(ArgumentOutOfRangeException outOfRange) when (outOfRange.ParamName == “dayOfWeek”)
{
   // do something about a bad day of the week
}
catch(ArgumentOutOfRangeException outOfRange) when (outOfRange.ParamName == “hour”)
{
   // do something about a bad hour
}
catch ArgumentOutOfRangeException outOfRange)
{
   // do something about some other bad parameter
}
…

As before, matches with catch blocks are attempted in source code order, but now the expression after the when has to evaluate to true as well as the type after the catch being compatible.

The introduction of when means you are less likely to need to define lots of new exception types.  Previously, you had to create a new type for each meaningful difference between exceptions, as far as filtering was concerned.  Now filtering can be based on a property of the type, so you could for instance have a single HTTP exception class, that contains an int property for the status code, and then filter based on the status code.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s