Preserving stacktrace information when processing exceptions with C# .NET
February 10, 2015 4 Comments
Have you ever tried to hunt down a bug and been blocked by an incomplete stacktrace? The stacktrace might point to a place in your code where you threw the exception with the “throw” keyword but there’s no information on where the exception was originally thrown.
This can happen if you process the exception within the catch clause, e.g. log it somewhere and then throw it back to the call stack.
Consider the following simple code where the first method calls the second which calls the next etc. and the last one throws an exception. The first method catches the exception, prints it to the Debug window and throws it:
public class CallStackExceptionDemo { public void RunStacktraceDemo() { try { DoSomething(); } catch (Exception ex) { Debug.WriteLine("Exception caught within RunStackTraceDemo. Here comes the stacktrace: "); Debug.WriteLine(ex.StackTrace); throw ex; } } private void DoSomething() { ContinueDoingIt(); } private void ContinueDoingIt() { FinishIt(); } private void FinishIt() { throw new NotImplementedException("Cannot yet finish the task."); } }
Let’s call RunStacktraceDemo from another part of the project:
try { CallStackExceptionDemo demo = new CallStackExceptionDemo(); demo.RunStacktraceDemo(); } catch (Exception ex) { Debug.WriteLine("Exception caught while running the stack trace demo. Here comes the stacktrace: "); Debug.WriteLine(ex.StackTrace); }
Let’s compare the two stacktraces:
A first chance exception of type ‘System.NotImplementedException’ occurred in Various.exe
Exception caught within RunStackTraceDemo. Here comes the stacktrace:
at Various.CallStackExceptionDemo.FinishIt() in c:\TestProjects\VariousCSharpLanguageConstructs\Various\Various\CallStackExceptionDemo.cs:line 38
at Various.CallStackExceptionDemo.ContinueDoingIt() in c:\TestProjects\VariousCSharpLanguageConstructs\Various\Various\CallStackExceptionDemo.cs:line 33
at Various.CallStackExceptionDemo.DoSomething() in c:\TestProjects\VariousCSharpLanguageConstructs\Various\Various\CallStackExceptionDemo.cs:line 28
at Various.CallStackExceptionDemo.RunStacktraceDemo() in c:\TestProjects\VariousCSharpLanguageConstructs\Various\Various\CallStackExceptionDemo.cs:line 16
…versus…
A first chance exception of type ‘System.NotImplementedException’ occurred in Various.exe
Exception caught while running the stack trace demo. Here comes the stacktrace:
at Various.CallStackExceptionDemo.RunStacktraceDemo() in c:\TestProjects\VariousCSharpLanguageConstructs\Various\Various\CallStackExceptionDemo.cs:line 22
at Various.Program.Main(String[] args) in c:\TestProjects\VariousCSharpLanguageConstructs\Various\Various\Program.cs:line 24
See, the second stacktrace is shorter, it only goes as far back as where we threw the exception within the catch clause.
How can we do the same and preserve the stacktrace? The solution is incredibly simple. Simply put “throw” instead of “throw ex” in the catch clause:
public void RunStacktraceDemo() { try { DoSomething(); } catch (Exception ex) { Debug.WriteLine("Exception caught within RunStackTraceDemo. Here comes the stacktrace: "); Debug.WriteLine(ex.StackTrace); throw; } }
Let’s rerun the program and compare the stacktraces:
A first chance exception of type ‘System.NotImplementedException’ occurred in Various.exe
Exception caught within RunStackTraceDemo. Here comes the stacktrace:
at Various.CallStackExceptionDemo.FinishIt() in c:\TestProjects\VariousCSharpLanguageConstructs\Various\Various\CallStackExceptionDemo.cs:line 38
at Various.CallStackExceptionDemo.ContinueDoingIt() in c:\TestProjects\VariousCSharpLanguageConstructs\Various\Various\CallStackExceptionDemo.cs:line 33
at Various.CallStackExceptionDemo.DoSomething() in c:\TestProjects\VariousCSharpLanguageConstructs\Various\Various\CallStackExceptionDemo.cs:line 28
at Various.CallStackExceptionDemo.RunStacktraceDemo() in c:\TestProjects\VariousCSharpLanguageConstructs\Various\Various\CallStackExceptionDemo.cs:line 16
…versus…
A first chance exception of type ‘System.NotImplementedException’ occurred in Various.exe
Exception caught while running the stack trace demo. Here comes the stacktrace:
at Various.CallStackExceptionDemo.FinishIt() in c:\TestProjects\VariousCSharpLanguageConstructs\Various\Various\CallStackExceptionDemo.cs:line 38
at Various.CallStackExceptionDemo.ContinueDoingIt() in c:\TestProjects\VariousCSharpLanguageConstructs\Various\Various\CallStackExceptionDemo.cs:line 33
at Various.CallStackExceptionDemo.DoSomething() in c:\TestProjects\VariousCSharpLanguageConstructs\Various\Various\CallStackExceptionDemo.cs:line 28
at Various.CallStackExceptionDemo.RunStacktraceDemo() in c:\TestProjects\VariousCSharpLanguageConstructs\Various\Various\CallStackExceptionDemo.cs:line 22
at Various.Program.Main(String[] args) in c:\TestProjects\VariousCSharpLanguageConstructs\Various\Various\Program.cs:line 24
Problem solved!
View all various C# language feature related posts here.
Hi Andras
Thank you for pointing on such crucial thing. People very often forget about the difference between ‘throw’ and ‘throw ex’.
By the way, in what cases we could need to cut off the previous stacktrace and use ‘throw ex’?
Hi Alex,
I cannot think of any case actually where cutting off parts of the stacktrace can be useful vs. keeping the stacktrace especially when trying to hunt down a bug.
//Andras
There is one problem with this. I would expect both stack traces to include line 16 (line 7 in the original code you included), the place in RunStacktraceDemo() where DoSomething() was actually called. Instead, the only place in RunStacktraceDemo() that is recorded is the place where the error was rethrown.
This blog post, http://thorarin.net/blog/post/2013/02/21/Preserving-Stack-Trace.aspx, contains the most succinct enumerations of the methods I’ve read to alleviate this and other problems. (At least I’m pretty sure other sites demonstrated how both solutions solved this particular problem.)
Hi John, thanks for your comments. //Andras