Exception filters in C# 6: their biggest advantage is not what you think

Very poorPoorAverageGoodExcellent (6 votes) 
Loading...

Exception filters are one of the major new features of C# 6. They take advantage of a CLR feature that was there from the start, but wasn’t used in C# until now. They allow you to specify a condition on a catch block:

static void Main()
{
    try
    {
        Foo.DoSomethingThatMightFail(null);
    }
    catch (MyException ex) when (ex.Code == 42)
    {
        Console.WriteLine("Error 42 occurred");
    }
}

As you might expect, the catch block will be entered if and only if ex.Code == 42. If the condition is not verified, the exception will bubble up the stack until it’s caught somewhere else or terminates the process.

At first glance, this feature doesn’t seem to bring anything really new. After all, it has always been possible to do this:

static void Main()
{
    try
    {
        Foo.DoSomethingThatMightFail(null);
    }
    catch (MyException ex)
    {
        if (ex.Code == 42)
            Console.WriteLine("Error 42 occurred");
        else
            throw;
    }
}

Since this piece of code is equivalent to the previous one, exception filters are just syntactic sugar, aren’t they? I mean, they are equivalent, right?

WRONG!

Stack unwinding

There is actually a subtle but important difference: exception filters don’t unwind the stack. OK, but what does that mean?

When you enter a catch block, the stack is unwound: this means that the stack frames for the method calls “deeper” than the current method are dropped. This implies that all information about current execution state in those stack frames is lost, making it harder to identify the root cause of the exception.

Let’s assume that DoSomethingThatMightFail throws a MyException with the code 123, and the debugger is configured to break only on uncaught exceptions.

  • In the code that doesn’t use exception filters, the catch block is always entered (based on the type of the exception), and the stack is immediately unwound. Since the exception doesn’t satisfy the condition, it is rethrown. So the debugger will break on the throw; in the catch block; no information on the execution state of the DoSomethingThatMightFail method will be available. In other words, we won’t know what was going on in the method that threw the exception.
  • In the code with exception filters, on the other hand, the filter won’t match, so the catch block won’t be entered at all, and the stack won’t be unwound. The debugger will break in the DoSomethingThatMightFail method, making it easy to see what was going on when the exception was thrown.

Of course, when you’re debugging directly in Visual Studio, you can configure the debugger to break as soon as an exception is thrown, whether it’s caught or not. But you don’t always have that luxury; for instance, if you’re debugging an application in production, you often have just a crash dump to work with, so the fact that the stack wasn’t unwound becomes very useful, since it lets you see what was going on in the method that threw the exception.

Stack vs. stack trace

You may have noticed that I talked about the stack, not the stack trace. Even though it’s common to refer to “the stack” when we mean “the stack trace”, they’re not the same thing. The call stack is a piece of memory allocated to the thread, that contains information for each method call: return address, arguments, and local variables. The stack trace is just a string that contains the names of the methods currently on the call stack (and the location in those methods, if debug symbols are available). The Exception.StackTrace property contains the stack trace as it was when the exception was thrown, and is not affected when the stack is unwound; if you rethrow the same exception with throw;, it is left untouched. It is only overwritten if you rethrow the exception with throw ex;. The stack itself, on the other hand, is unwound when a catch block is entered, as discussed above.

Side effects

It’s interesting to note that an exception filter can contain any expression that returns a bool (well, almost… you can’t use await, for instance). It can be an inline condition, a property, a method call, etc. Technically, there’s nothing to prevent you from causing side effects in the exception filter. In most cases, I would strongly advise against doing that, as it can cause very confusing behavior; it can become really hard to understand the order in which things are executed. However, there is a common scenario that could benefit from side effects in exception filters: logging. You could easily create a method that logs the exception and returns false so that the catch block is not entered. This would allow logging exceptions on the fly without actually catching them, hence without unwinding the stack:

try
{
    DoSomethingThatMightFail(s);
}
catch (Exception ex) when (Log(ex, "An error occurred"))
{
    // this catch block will never be reached
}

...

static bool Log(Exception ex, string message, params object[] args)
{
    Debug.Print(message, args);
    return false;
}

Conclusion

As you can see, exception filters are not just syntactic sugar. Contrary to most C# 6 features, they’re not really a “coding” feature (in that they don’t make the code significantly clearer), but rather a “debugging” feature. Correctly understood and used, they can make it much easier to diagnose problems in your code.

8 Comments

  1. Alois Kraus says:

    Very nice writeup. But you did forget the most important use case for exception filters. It is not logging but writing a memory dump in the filter to get the possibility to see all local variables which did lead to the exception before the stack is unwound.
    In MSVC we have the __try/__except keywords for the very same reason.
    If you are after a sporadic issue this approach can help you a lot if you e.g. are after buggy graphics card drivers.

  2. Philip says:

    Thanks for your sharing. I use stack trace all the time, even when logging for exception investigation. Since you mentioned that as long as using “throw”, stack trace is the same. My question is, if I just use stack trace, there is no difference between given 2 examples for my use case. Is there any other difference, like performance? and when we will use Stack? can you give an example? thanks.

    • Thomas Levesque says:

      Hi Philip,

      The stack trace contains very little information: just the names of the methods on the stack, and file name and line number if you have debug symbols. The only thing it tells you is where the exception was thrown. It doesn’t tell you why the exception was thrown.

      But if you have the stack intact (in a crash dump, or when you’re debugging directy in Visual Studio), you can examine the state of the variables in the method that threw the exception. This makes it much easier to understand why the code failed.

  3. Andrej says:

    Hi Thomas, nice ;-). I would add that it makes the code look much cleaner in situations, where the exception catching is actually an expected code flow. Although it’s of course discouraged as of the performance hit and readability, there are sometimes situations when try/catch is still the faster/nicer way of doing things. As f.e. in deserializing an unknown amount of data from ZipArchiveEntry: it hides the aggregated stream for checking the read position and the reflection of this would cost more than to expect the exeption in an endless loop – which looks withing the filter rule pretty clean, like catch (Exception ex) when (ex.Message == “Root element is missing.” && rowCount > 0). Greetz from Swiss, Andrej

    • Thomas Levesque says:

      Hi Andrej, good point! However I wouldn’t base any logic on the exception message, because it can change depending on the current culture…

  4. SGunni says:

    Hi Thomas,

    I have a logic similar to the below snippet.

    public void method1()
    {
    try
    {
    method2();

    }
    catch()//with exception filters

    }
    public void method2()
    {
    try
    {
    method3();
    }
    catch (Exception )//with exception filters
    { }
    }

    1) An Exception Exception1 is generated from Method3. It is caught in the Method2 Catch Block as it matched the filter.

    When the control entered Method2 Catch, what would be the state of Stack and Stack trace?

    2)An Exception Exception2 is generated in Method2.It is not caught in Method2 as it did not match with filter.

    How does the filter is helping me here? Stack still holds information about Method3 and Method2?

    • Thomas Levesque says:

      Hi,

      1) An Exception Exception1 is generated from Method3. It is caught in the Method2 Catch Block as it matched the filter.

      When the control entered Method2 Catch, what would be the state of Stack and Stack trace?

      The stack is unwound when you enter a catch block, so at this point the top of the stack is Method2. The stack trace, on the other hand, doesn’t change when you enter a catch block; it contains the state of the stack when the exception was thrown, i.e. the method at the top of the stack trace is Method3.

      2)An Exception Exception2 is generated in Method2.It is not caught in Method2 as it did not match with filter.

      How does the filter is helping me here? Stack still holds information about Method3 and Method2?

      Why would it contain information about Method3 if the exception was thrown in Method2 ? I don’t really understand what you’re asking here…

3 Trackbacks

Leave a comment

css.php