Discount for my course: High Performance Coding with .NET Core and C#

Gergely Kalapos


C# Exception Filters - .NET Concept of the Week - Episode 14

Posted on May 25, 2018



In this episode we talk about exception filters in C#. We will take a look at the generated IL code and we will also discuss that entering a catch block always unwinds the stack, which makes debugging harder and also can hurt performance.
With an exception filter we can avoid this!


The written form of the video:

Let's start with the syntax:

class Program
{
    public static void Main(String[] args)
    {
        Method_Filter();
    }

    static void Method_Filter()
    {
        try
        {
            MethodThrowing();
        }
        catch(Exception e) when (e.Message == "Bamm")
        {
            Console.WriteLine("BammException");
        }
    }

    static void MethodThrowing() => throw new Exception("Bamm");
}
 

Since C# 6 after the catch block we can use the when keyword and with this we can say that we only want to catch exceptions which have “Bamm” in their message property.
This here is of course completely made up and I don’t think that you want to filter exceptions based on the message “Bamm”, but for our discussion this is fine. A more real-world use case would be a custom exception class with some properties and filtering these custom exceptions based on the values stored in those properties.

All right, so this one here is an exception filter, and when an exception within the MethodThrowing method is thrown with the message “Bamm” then we catch it, otherwise the exception will bubble to the Main method and then eventually the program will terminate with an unhandled exception.

Filters vs. rethrowing an exception

Now you may think that this is just a syntactic sugar and we could also write a simple catch block and if the exception message is “Bamm” then we do the same as before, otherwise we rethrow the exception. These two aren’t the same!

static void Method_PlainCatchBlock()
{
    try
    {
        MethodThrowing();
    }
    catch (Exception e)
    {
        if (e.Message == "Bamm")
        {
            Console.WriteLine("BammException");
        }
        else
        {
            throw;
        }
    }
}

The behavior in both cases is very similar. If the exception message is “Bamm” then we print “BammException” to the console, otherwise we let the exception to bubble up to the Main method.

Now let’s see the compiled code to understand the difference! I opened the compiled code in IL DASM, first let’s look at the method with the plain catch block:

Nothing unexpected here, we have a try block, within this try block we call our method that throws an exception and for this try block we also have a catch block.

Within this catch block we have our string comparison, and if the string matches to “Bamm” we call Console.WriteLine, otherwise rethrow the exception.

Now let’s take a look at the other method with the exception filter!

We have our try block here and after that we have a filter block, and after that we have a catch block. This filter block compares the exception message to the string "Bamm" and if the comparison returns true then the catch block is executed.

The main difference is that in this case is that we don’t enter the catch block if the filter returns false, which in this case means that the exception message isn’t “Bamm”.

This means that this exception filter syntax in C# isn’t just a syntactic sugar. The part after the “when” keyword is compiled into an exception filter and with that we can decide whether the catch block should be entered or not.

ECMA-335 - history

Now this exception filter mechanism that we see in the IL code was already available in the first CLR version. In fact, in Visual Basic you could already use exception filters and generate the IL code above, but in C# this was only introduced in Version 6.

The ECMA-335 standard defines the Common Language Infrastructure (CLI). This document basically describes what IL code is valid and what the CLR does with specific IL instructions.

And “II.19.4 Filter blocks” defines the filter block that we saw in our own IL code. Keep in mind that this document doesn’t talk about C#, this talks about the IL code that the C# code compiles to and as already mentioned, this "filter block" definition was here in the first version of this document.

All right, with that now we understand what these “when” blocks compile to, now let’s see what this means in practice!

Observing exceptions with filters

For this let’s look at another use case where exception filters are very useful. Let’s say we want to log exceptions. We don’t want to catch them or do anything with them, we simply want to log them. This typically can be a logging framework, which don’t want to influence the code, it simply wants to log exceptions. To keep this short we won’t create a logging framework here, we simply print the exceptions to the console.

class Program
{
    static void Main(string[] args)
    {
        var stateTimer = 
             new Timer(GetCallStack.PrintCallStack, Thread.CurrentThread, 1000, 0);
        A();
        Console.ReadKey();
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    static void A()
    {
        B_WithExceptionHandler();
    }

    static void B_WithFilter()
    {
        try
        {
            C();
        }
        catch (Exception e) when (Log(e)){}
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    static void C()
    {
        var date = DateTime.UtcNow;
        throw new Exception("Bamm");
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    static bool Log(Exception e)
    {
        Console.WriteLine($"Exception: {e.Message}");
        //Thread.Sleep(50000000);
        return false;
    }
}

One cool thing about these exception filters is that we can put anything after the when keyword that returns a Boolean. If the return value there is true then the catch block is executed, otherwise we simply skip the catch block and the exception bubbles further up to the caller. The other cool thing is that we have access to the exception instance within these when clauses. There is a Log method in the code above that prints the exception to the console and then returns false, which means that the catch block won’t be executed.

Basically, we can observe the exception, but we don’t catch it, so the caller method must handle it, or the program will terminate.

And here is another version, which doesn’t use an exception filter. We simply catch the exception, log it, and then rethrow it.

[MethodImpl(MethodImplOptions.NoInlining)]
 static void B_WithExceptionHandler()
 {
     try
     {
         C();
     }
     catch (Exception e)
     {
         Log(e);
         throw;
     }
 }

We already know that the generated IL code will be different, now let’s discuss what the consequence is in practice.

One important thing to note here is that once a catch block is entered the CLR will unwind the stack. This means that if we hit the first line in the catch block then the method with the catch block (B_WithExceptionHandler) will be on the top of the call stack. So, the method that we called in the try block (in this case C) simply won’t be visible on the call stack.
This has 2 implications:

  • Unwinding the stack costs CPU
  • It makes debugging harder, since you destroy the original call stack.

Stack unwinding - What happens with the call stack?

Let’s start with the call stack! To see the difference, I uncommented the Thread.Sleep line in the log method. Additionally, I start a timer, which will fire after 1 second, so at that point we are already in the log method, where we observe the exception and what we do in the timer is that we simply print the call stack of the thread where we observe the exception.

class GetCallStack
{
    public static void PrintCallStack(Object o)
    {
        var thread = o as Thread;
        thread.Suspend();
        System.Diagnostics.StackTrace s = new StackTrace(thread, false);            
        Console.WriteLine("======StackTrace=====");
        Console.WriteLine(s.ToString());
        Console.WriteLine("");
        thread.Resume();
    }
}
Now this code isn’t how you should capture call stacks, this is actually deprecated, but for us right now this is fine. First, I’m going to use the method with the exception filter:


We clearly see that Main called method A then it called B_WithFilter then C and then we are again back at B_WithFilter and currently we are in Thread.SleepInternal which was called through the Log method. So, we throw the exception in the C method and it is on the call stack and this is because we did not enter the catch block here in B_WithFilter, so we didn’t unwound the stack.

Another good thing about this is that if we let’s say have something more within the method where the exception is thrown, and we simply start the debugger which breaks when we hit the throw line then we can even see the local variables here. In this case I use the default “Break When Thrown” settings, so my debugger only breaks on second chance exception. In a more complex code this can be very useful.

All right, now let’s call the other method that does not use exception filter. This is the call stack:

Main, A, B_WithExceptionHandler, and then the Log method. There is nothing here about the C method that throws the exception and this is because we entered the catch block which did unwind the call stack.

Let’s also start the program with the debugger:


As you can see with the default settings it only breaks in the catch block when we rethrow the exception, but we don’t really know what happened in the C method and again, the reason for that is that as soon as we entered the catch block the CLR unwound the call stack. Of course, you can also change the settings here to also break on first change exceptions as a workaround, but that doesn’t change the fact that the catch block destroyed the original call stack.

Now one thing I would like to point out here is that the StackTrace property on the exception instance always contains the same call stack, so in this code both with the B_WithFilter and the B_WihExceptionHandler methods the StackTrace property contains the C method.


Stack unwinding doesn’t change that, but it changes the stack when you observe the CLR from the outside, for example with another tool like PerfView, or when the process is dumped into a crash dump, or when you create a stack trace manually as we did it earlier. In scenarios when for example the program crashes in production a stack unwinding in a similar code can make the developers job to figure out why the program crashed much harder.

Stack unwinding - performance

Now let’s talk about the other aspect of stack unwinding which is performance. For this I prepared another small sample application:

public class Program
{
    static void Main(string[] args)
    {
        var summary = BenchmarkRunner.Run();
    }

    [Benchmark]
    public void ExceptionsWithCatchBlock()
    {
        for (int i = 0; i < 100; i++)
        {
            try
            {
                CatchBlockImpl();
            }
            catch
            {

            }
        }
    }

    public void CatchBlockImpl()
    {           
        try
        {
            MWithException();
        }
        catch (Exception e)
        {
            Debug.WriteLine($"Exception catch {e.Message}");
            throw;
        }

    }

    [Benchmark]
    public void ExceptionsWithFilter()
    {
        for (int i = 0; i < 100; i++)
        {
            try
            {
                FilterImpl();
            }
            catch
            {

            }
        }
    }

    public void FilterImpl()
    {
        try
        {
            MWithException();
        }
        catch (Exception e) when (Log(e)){ }

        bool Log(Exception e)
        {
            Debug.WriteLine($"Exception filter {e.Message}");
            return false;
        }
    }

    static void MWithException() => throw new Exception("Bamm");
}

It’s basically the same as before: we want to log our exceptions, but we don’t want to catch them. We have one sample with the classic “catch and rethrow” approach, then there is another one with an exception filter which avoids stack unwinding. I use BenchmarkDotNet to measure the difference between the two. For this I have 2 [Benchmark] methods. One calls the solution with the filter 100 times and the other one calls the solution with the catch block and rethrow also 100 times. In both cases we catch the exception in the benchmark method and don’t do anything with it.

Let’s see the numbers!


As you can see the difference is significant. This code of course doesn’t do anything except it throws the exception, so in a real-world code base I’d expect a smaller difference, but this is still significant.

So, let’s summarize this:

  • Since C# 6 we can use exception filters and the big advantage of this feature is that it prevents stack unwinding.
  • The positive effect of this is that every time you filter out exceptions with an exception filter you keep the call stack as it is, which makes debugging easier and also saves performance.

Links:

Source code (under 14_CsExceptionFilter)
ECMA-335
Full playlist with other episodes: on YouTube

Music: bensound.com


;