Preserving stacktrace information when processing exceptions with C# .NET

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.

Advertisement

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

4 Responses to Preserving stacktrace information when processing exceptions with C# .NET

  1. Alexander says:

    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’?

    • Andras Nemes says:

      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

  2. John Chesshir says:

    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.)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Elliot Balynn's Blog

A directory of wonderful thoughts

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT TUTORIALS WITH OPEN-SOURCE PROJECTS

Once Upon a Camayoc

Bite-size insight on Cyber Security for the not too technical.

%d bloggers like this: