Jeremy Davis
Jeremy Davis
Sitecore, C# and web development
Article printed from: https://blog.jermdavis.dev/posts/2022/csharp-catch-when-contextual-exception-filter

Discovering C# exception filters

An older language feature I'd not noticed

Published 21 November 2022
.Net C# ~2 min. read

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

What are these filters?

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
}

					

An interesting use for this...

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:

The VS debugger showing an automatic variable for a caught 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.

So I may not be at the cutting edge of C#, but I feel like I learned a few things this week...
↑ Back to top