There's been a lot going on with language development in C# over the last couple of years. But despite all the current change for things like record types and generic maths, there are some changes from older versions that I somehow missed. This week social media put me on to the concept of contextual filters for exception handling. What's that? Read on...
When you need to catch an exception, the standard approach is that you decide what exception types you want to handle with your
catch
statement(s):
try { // do something } catch(SpecifcExceptionType ex) { // handle the error }
So if a
SpecificTypeException
gets throw, the handler code here will be triggered, but if a
DifferentException
got thrown it would not. And that's all great most of the time, but what happens if you need to make decisions on what to handle based on more than just the type? Maybe the exception has a property you want to test before you decide to catch it or not?
You could write this as:
try { // do something } catch(SpecifcExceptionType ex) { if(ex.SomeParameter == true) { // handle the error } }
That works, but it doesn't really fit with the style that the language designers have been going for in C# of late. And it turns out there's a specific language feature that can be used for this with C# 6 and better: The 'when' execption filter. And using that you can rewrite this bit of code as:
try { // do something } catch(SpecifcExceptionType ex) when (ex.SomeParameter == true) { // handle the error }
One interesting thing that works here is that doesn't work with the more basic pattern from before is that you can have multiple catch/when statements for the same exception type if you need to. For example:
try { // do something } catch(SpecifcExceptionType ex) when (ex.SomeParameter == 1) { // handle the error one way } catch(SpecifcExceptionType ex) when (ex.SomeParameter == 2) { // handle the error another way }
Over the years, I think I've written this a few times when I'm debugging an error:
try { // do something } catch(Exception ex) { int i = 3; }
And stuck a breakpoint onto the
int
declaration... So when I run the code, the debugger will break if an exception is throws.
But it's a pretty obvious hack, which I always need to get rid of it before I commit anything.
But it turns out there's a better way to write this. Alongside the
when
clause above, .Net also has a
Debugger
static class which can be used to detect and interact with an attached debugger. So you can write this instead:
try { // do something } catch(Exception ex) when (Debugger.IsAttached) { Debugger.Break(); }
If the debugger is attached to the process, this code will catch and break when the exception occurs. But if the debugger is not attached it will let the exception propagate up the call tree.
Though what you'll note here is that the compiler will raise a warning on the declaration of
ex
because it's never used. I used to think I needed this to be able to see the exception. But there are two different things that can be done to get rid of that warning. One is to explicitly get rid of
ex
using another rarer language feature, and change the catch block to:
try { // do something } catch(Exception ex) when (Debugger.IsAttached) { _ = ex; Debugger.Break(); }
The
_
in C# 7 and later is the "discard operator". That means "a variable that I don't care about, so you can throw it away". Using that is enough to make the compiler think you have made use of the
ex
variable, and hence get rid of the warning.
But it turns out you don't need
ex
at all in VS2022 (and maybe earlier - I've not checked other versions). If you declare the code as:
try { // do something } catch when (Debugger.IsAttached) { Debugger.Break(); }
Then the debugger still keeps an "local" variable in the watches panel which can be used to look at the exception:
And you can also write something like
catch(NetworkException) when (Debugger.IsAttached)
if you want to filter by exception type but not bother declaring a variable.