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.
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.
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.