< prevpermalinknext >

A neat Trick to Debug Exceptions in C#

Programming · Aug 24th, 2021

The Problem

Assume we have a method called UnsafeMethod(), which throws an exception upon calling it. What we want to do is to debug this method and find out where this exception comes from. The naïve approach is to simply surround it with a try-catch, and set a debug point in the catch-block:

try
{
    UnsafeMethod();
}
catch (Exception e)
{
    Console.WriteLine(e);
    throw;
}

When the debugger jumps into the catch-block, we know that an exception has been thrown, and we get the exception-object which has been generated. While this may seem okay at first, it isn’t ideal. If the exception comes from lesser written code, the exception itself may proof to be very unhelpful to diagnose the problem. More importantly: Even though we know that UnsafeMethod() threw the exception, if the method is hundreds of lines long and it calls various other methods, we still don’t know where that exception came from.

The latter reason happens by design. When an exception is thrown, execution of the program is stopped and an exception-object is handed up the callstack, until some code handles it via a try-catch. This means an exception naturally collapses the callstack. When the program reaches the catch-block for whatever reason, everything that happened in the try-block is now collapsed and impossible to retrieve. But there is a way to look into the state of the try-block, before the catch-block is even executed.

The Solution

try
{
    UnsafeMethod();
}
catch (Exception e) when (new Func<bool>(() => true)())
{
    Console.WriteLine(e);
    throw;
}

If we use the when-statement, put inside a delegate which returns a bool and set a debug point inside that delegate, then we can look into the try-block. When the program reaches the delegate, then we can simply use the debugger and look into the callstack, and see where exactly the exception came from.

So, what is the when-statement and why does this work? The when-statement allows the catch-block to be executed conditionally. If the condition evaluates to true, the exception is caught and the catch-block is executed. If the condition evaluates to false, then the exception is NOT caught and continues to be handed up the callstack.

Because of the when-statement, two different scenarios can happen: Either the exception is being caught, or it is being thrown at the original position. Because the when-statement can cause these two different behaviors, the callstack has to be kept intact during the evaluation of the when-statement, just in case the exception is not being caught. And this is why our trick works: Because we put a delegate as the condition in the when-statement, we can halt the evaluation of the when-statement and then look into the still intact callstack. Thus, if the debugger reaches our delegate, the state of the try-block is conserved.

Alternatively, you may want to write a method, which makes it a bit easier for you:

public bool LogException(Exception e, bool shouldCatch = false)
{
    Console.WriteLine(e);
    return shouldCatch;
}

And then use it like this:

try
{
    UnsafeMethod();
}
catch (Exception e) when (LogException(e)) { }

With this, the exception is always logged. If you hand the method true via the method parameter, the exception will be suppressed. And whenever you want to debug the exception, you can put a debug point in the body of LogException() and look easily into the callstack.

And that's it. It's really a quite neat and easy to use trick, and my goto way of debugging exceptions. Have fun debugging 😊

< Previous Post: Improving Website Performance
> More Programming related Posts
> permalink