C#6: Exception Filters

The for the last six1 releases the C# compiler has been keeping part of the .NET Framework secret from us2; exception filters. It turns out that the .NET Framework has supported exception filters since the very beginning, there was just no way to express them using C# until now.

C#6 adds the when keyword for use in try/catch blocks to specify exception filters. An exception filter is a predicate method that takes the thrown exception and returns true when the exception should be caught or false when it should not. If the filter says the exception should not be caught, the underlying system can continue to throw it.

This allows us to reduce the complexity in our code as we can put multiple catch statements with different filtering rules in the same try/catch block. This gives a switch-style approach to exception handling that is supported at the lowest level, reducing the need to rethrow exceptions (or to remember the difference between throw and throw exceptionVar;)3.

Here is a try/catch block showing an example of exception filtering:

Func<ArgumentException,string,bool> filterParameterName = (e,s) => e.ParamName == s;
try
{
    CallSomething("param1", "param2", "param3", "param4");
}
catch (ArgumentException ex) when (ex.ParamName == "param1")
{
    Console.WriteLine("Filtered: param1");
}
catch (ArgumentException ex) when (filterParameterName(ex, "param2"))
{
    Console.WriteLine("Filtered: param2");
}
catch (ArgumentException ex)
{
    Console.WriteLine($"Unfiltered: {ex.ParamName}");
}

Before I continue, I must state that this is a completely contrived example for demonstrable purposes; your filters would probably act on more than just the value of a string, the two filters shown would use the same code, and the handling would involve different things in each catch4.

Now, some things to note. First, the parentheses around the when condition are mandatory; you don't need to remember this as the compiler and syntax highlighting will remind you. Second, the content of the when condition must evaluate to bool; you cannot specify a lambda expression here. I am certain most of you already assumed that, but for some reason, I felt like that should be possible. However,  when is akin to if or while, so it makes sense that a lambda expression would not work.

The example above provides three different catch blocks for the exact same exception type, ArgumentException. Each filter is evaluated in the order specified, so, if CallSomething() threw an ArgumentException with ParamName set to param2, the when condition on the first catch would reject it, but the second filter would catch it and handle accordingly. A ParamName value filtered out of the first two catch blocks would fall into the last.

In conclusion

Exception filtering is a useful and simple concept that should help to make exception handling easier to write. While some kind of filtering could be achieved before using conditions and throw inside of catch blocks, this language support now means that exception handlers (the content of catch blocks) have a single responsibility and the catch statements themselves are entirely responsible for declaring what must be caught. It also means that the exception handling within the .NET framework can be entirely responsible for routing exceptions in C#-implemented applications.

Exception filters have been supported by VB.NET and .NET-supporting variants of C++ since the versions released alongside .NET Framework 1.1; now, as of C#6, they are supported by C# too.

  1. 1.0, 1.2, 2.0, 3.0, 4.0, 5.0 []
  2. Actually, it's been keeping several, but we can't have everything []
  3. The first rethrows the original exception with the stack unchanged, the second throws a new exception and resets the stack []
  4. otherwise, why filter? []

6 thoughts on “C#6: Exception Filters”

  1. Nice and short, just the way I like it. But what I do miss is the Exception as the last catch. If anything else happens except ArgumentException the application will crash. Not sure if this is on purpose in your example.

    1. That's deliberate. It's generally best practice to only catch the exceptions you expect and can handle gracefully. The only place where a catch-all is generally appropriate is at the very top level of your app for implementing a crash reporting feature. That way, anything that happens that was not intended is reported as a bug to be fixed. If we always caught every exception, we hide that information and potentially leave apps in a buggy state.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.