Rethrow Exceptions with ExceptionDispatchInfo
What I am going to be talking about today is neither new nor complicated. But doing code review, I still see many developers and even experienced ones make this mistake while handling exceptions.
Let's start from the scratch. What is wrong with the code below?
public static void DoSomething(int number) { if (number < 0) { throw new ArgumentException("number must not be negative!"); } } public static void Main() { try { DoSomething(-1); } catch (Exception ex) { //Log Exception throw ex; }
What we do here is to log the exception in case of an error and the rethrow it. But the problem here is that when the line 'throw ex;' is executed, the stack trace of the original exception will be gone and a new one from this point will be replaced!
Content of exception before 'throw ex;':
System.ArgumentException: number must not be negative!\r\n at Program.DoSomething(Int32 number) in C:\\Users\\MyUser\\Documents\\Visual Studio 2017\\Projects\\Console2017\\Console2017\\Program.cs:line 16\r\n at Program.Main() in C:\\Users\\Morteza\\Documents\\Visual Studio 2017\\Projects\\Console2007\\Console2007\\Program.cs:line 24
Content of exception after 'throw ex;':
System.ArgumentException: number must not be negative!\r\n at Program.Main() in C:\\Users\\MyUser\\Documents\\Visual Studio 2017\\Projects\\Console2017\\Console2017\\Program.cs:line 32
Do you see the difference between these two? In the first one we see the original exception which was thrown from DoSomething and in the second one, that's implied that exception was thrown in Main method. This could simply be misleading and waste time during debugging the code.
To rethrow the exception without changing anything, we just need to use only 'throw;' (without ex) as follow:
public static void Main() { try { DoSomething(-1); } catch (Exception ex) { //Log Exception throw; } }
But this is not end of the story. Sometimes for whatever reason we need to do something with exception outside of the catch block. For example, before C# 6.0, it was not possible to use await keyword in the catch block. Then we would have to do something like the code below (actually I have seen this a lot while reviwing code in different sources):
public async static void Main() { Exception exception = null; try { DoSomething(-1); } catch (Exception ex) { exception = ex; } if (exception != null) { await LogExceptionAsync(exception); throw exception; } }
Well, to be able to use await keyword, we had to play around the exception outside of the catch block. Same problem exists in this scenario but we can't simply use 'throw;' here since we are outside of catch block, and it will lead to a compile error.
This is where ExceptionDispatchInfo class comes to the rescue. With this class we can simply capture the original exception and rethrow it anywhere without losing any data.
public async static void Main() { ExceptionDispatchInfo exceptionInfo = null; try { DoSomething(-1); } catch (Exception ex) { exceptionInfo = ExceptionDispatchInfo.Capture(ex); } if (exceptionInfo != null) { await LogExceptionAsync(exceptionInfo.SourceException); exceptionInfo.Throw(); } }
I think the code is crystal clear. We first capture the original exception in the catch block and then throw it at some point later.
Happy Programming!