Exception filters in C# 6

This is the fourth part of a series of posts I’m making into Upcoming Language Feature Changes in C# 6.

I must admit, I didn’t initially understand the fuss with exception filters. I saw a few tweets from along the lines of “finally, C# gets exception filters like VB.NET and F#” but didn’t understand the excitement. I saw code examples like the following and dismissed it.

    public void Demo1(int randomNumber)
    {
        try
        {
            if (randomNumber % 2 == 0)
            {
                throw new Exception("Even");
            }
            else
            {
                throw new Exception("Odd");
            }
        }
        catch (Exception e) when (e.Message == "Even")
        {
            // Extra logging because something evil happens when Even
            Console.WriteLine("Extra Logging");
        }
        catch (Exception e) when (e.Message == "Odd")
        {
            throw;
        }
    }

The change looks to be quite simple and pretty much syntactic sugar to save us putting “if/else” or “switch” statements inside a catch block, but it’s actually much more useful and subtle.

Important yet subtle improvements

First, you can put anything inside the “when” statement as long as it returns a bool. True means the exception is caught inside the corresponding catch block, false means the exception is passed to the next catch block.

Second, and more importantly, exception filters are checking the exception before it’s caught. This means the stack trace is not unwound, preserving the line number the actual exception occurred, rather than reporting the line of the subsequent “throw” statement.

Let’s look at some code to explain that in more detail.

As usual, it’s difficult to come up with something meaningful yet simple enough to fit on one screen, so please go with it and hopefully your imagination will stretch to see how this could happen in a real code base!

Demo – Extra Logs – without exception filters

Sticking with the above example, let’s imagine something strange happens when the method receives an even number, so you decide to add some extra logging inside the catch block and re-throw:

public void WithoutFilters(int randomNumber)
{
    try
    {
        if (randomNumber % 2 == 0)
        {
            throw new Exception("Even");
        }
        else
        {
            throw new Exception("Odd");
        }
    }
    catch (Exception e)
    {
        if (e.Message == "Even")
        {
            Console.WriteLine("Extra Logging");
        }

        throw;
    }
}

You get the desired result, but the stack trace reports the exception coming from the final “throw” statement which isn’t exactly helpful.

Demo – Extra logs – With Exception Filters

Let’s update the code to use exception filters, taking advantage of the fact we can put a method in the “when”:

public void WithFilters(int randomNumber)
{
    try
    {
        if (randomNumber % 2 == 0)
        {
            throw new Exception("Even");
        }
        else
        {
            throw new Exception("Odd");
        }
    }
    catch (Exception e) when (Log(e))
    {
        // as log returns false, this never gets run
    }
}

public bool Log(Exception e)
{
    // Log something
    if (e.Message == "Even")
    {
        Console.WriteLine("Extra Logging");
    }

    return false;
}

Here, the Log method is called for every exception but as it returns false, the corresponding catch block is never entered. This means we get the logging required but the exception isn’t re-thrown, so the stack trace contains the correct line number.

I also think this is easier to read and should hopefully lead to less cut-and-paste code inside catch blocks.

Summary

Although not obvious benefit of exception filters, if used wisely, this new language feature should lead to better exception handling, a better debugging experience and more maintainable code.

I suspect I’ve missed other uses for this change, so let me know in the comments below if I have!

Leave a Reply

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