Advanced Exception Handling in C# and .NET Applications

Backtrace has been working with C# and .NET developers for past year to develop a crash and exception handling library that allows you to understand better an exception generated by any .NET application. We would now like to share with you our knowledge that will allow you to better handle errors and deliver any .NET application more efficiently and effectively to your customers. I would like to illustrate a few methods on how to handle specific C# errors based on the experience from our projects.

To better understand why this is important, I have prepared a brief tutorial. It’s a simple application that tries to parse a string of arguments:

In this article, I will discuss 6 key issues that developers often see while debugging their code, and offer suggestions for how to make the code easier to debug.

  • Missing stack trace when throwing exception from try/catch block.
  • The difference between Environment.StackTrace and Exception stack trace
  • Getting exception type from exception object
  • Using Exception Filters to catch different types of exceptions
  • Handling aggregate exceptions
  • Getting line numbers in production code stack traces

Missing Stack Trace When Throwing Exception in C#

Let’s look carefully into try/catch block in the above sample code. When a developer prepares exception handling code, he can recognize that an exception which is printed on standard output is missing some important information.

Missing Stack Trace

From the information above, we do not understand exactly where the exception occurred. We know what the issue was because we can still use the information above to find what’s wrong; however, the generated stack trace does not contain the most important stack frames. Why did this occur? In our sample code, we are trying to rethrow an exception from try/catch block by using command throw e;. Thus, the throw command will recreate a stack trace from the place of the code where the developer uses command. What does it mean? If you receive an exception and then rethrow it, you will miss stack frames from try/catch block to exception.

What should we do to rethrow an exception with missing stack frames? In try/catch block you can use a throw command that will automatically rethrow exception from the try/catch to calling methods. Let’s see what the output from our sample code would look like when we replace throw e; with throw;

Complete Stack Trace

Now, we can understand much more about the exceptions and we can find what caused it – our tryParse method. Please pay attention for missing stack frames. Next, we can navigate to a faulting code to fix all existing issues.

Environment Stack Trace vs Exception Stack Trace

In the example above, our exception was not handled by any try/catch block after rethrowing it. When we try to print an exception from first try/catch block, we will miss some stack frames. Did we omit something? The reason is that an exception object stores only stack frames from try/catch block. So how can we still get stack trace from an application start to try/catch block? .NET allows you to create a StackTrace object that will collect stack frames from application start to the current moment:

var stackTrace = new StackTrace();

With the information provided above, you can recreate all the steps so that the application will create an exception from the very beginning of the try/catch block.

When we created a stack trace parser in a Backtrace C# library, we added support for environment stack frames. Later, we will recognize that in most cases, stack frames are not so useful for developers. Modern .NET requires you to create asynchronous code with async/await. In this case async state machine generates a lot of useless stack frames that will not allow you to learn what your application has done to throw an exception. Even after cleaning, the generated stack trace, by removing stack frames from Microsoft libraries, it still does not generate useful information for the developers.

We learn that the most important part for every developer is inside try/catch block. If you still need to include environment stack trace, you can still add a stack frame to the existing stack trace. Please keep in mind that by removing the stack frames and customizing the modification you can change the way how the deduplication algorithm works. For other exceptions we suggest using the global exception handler which should catch all other exceptions.

Getting Exception Type Information From the Exception Object

In our previous example from exception information we know what caused the exception by using an exception classifier and an exception message. Stack frames allows us to localize the faulting code and now we can change the wrong code easily. In some cases, we want to reprocess an exception information, but we still want to handle all possible exceptions. To achieve this, we can still use an exception filter (we will talk about this feature later….) or we can try to learn about exception by using reflection. How can we get the exception type (the classifier) from the exception object in a try catch block via reflection methods?

var exceptionType = e.GetType().FullName;

Reflection allows you to get much more information about an exception than you think. If you are not familiar with reflection mechanism in C#, check what reflection API prepares for you in gif below.

Execution Types

Our C# library, using the reflection available property, allows us to generate the correct type of a report. You can imagine how we can use an exception to generate an exceptionType information, but did you know that we can collect information about the faulting assembly? The reflection mechanism in this case allows us to collect program attributes or to detect faulting assembly name.

Using Exception Filters to Catch Different Types of Exceptions

C# 6.0 allows you to create an exception filter. What does it mean? You are probably familiar with syntax, where you can use multiple catch to handle different types of exceptions. You might want to use different catch clauses for the same exception based on an exception property. It could be solved using reflection tricks that were described above, but now you can also use the new C# feature – Exception Filters. If you need more control on exception handling, you can write simple arguments to invoke a specific catch block. In this case, I prefer to filter the exception by a method that returns true/false for a specific exception type. How can I use exception filters in my application? It’s simple – We can change the exception handling code to include a when clause:

Handling Aggregate Exceptions

If you are familiar with an Entity Framework and ASP.NET you probably receive one specific type of exception – AggregateException. This type of an exception aggregates one or more exceptions in the exception container. How do you check exceptions? AggregateException has the property InnerException that contains all the exception objects.

When we developed Backtrace’s support for .NET, we decided to ensure support for every type of exception – including AggregateException. In our early releases, we recognized AggregateException reports did not provide enough information for our users. Why? Because they only receive information about the existing exceptions, nothing more. After that, we decided to ensure support for unpacking exceptions from AggregateException and report on the detailed information that developers really care about.

Getting Line Numbers in Production Code Stack Traces

Our first example, regarding print exception information to standard output. With minor modifications to our code, we have a chance to navigate to the faulting source code and change the wrong library behavior. When we ship the application to production we could have had a much harder time than now – why? When you share your application with special .pdb files, the release mode of your application will not generate a stack trace with the line number information.  

What happened behind the scene? When you debugged your application locally, Visual Studio uses.pdb files to provide line number information  your stack trace. Now, when you compile and make the release build of your application, the .NET platform cannot use missing .pdb files. This will cause a missing stack trace.

So, is there a way to recognize where my code failed? Yes! Backtrace C# integration provides ILOffset a property that contains information to address the faulting code. With this information you can use the .pdb file to check what went wrong.

For deeper introspection, Backtrace also provides minidump support. You can use this file in the Backtrace Debugger or downloaded into Visual Studio to check:

  • Where your code throws exception?
  • What was the value of variables in the current scope?
  • What was the application state?

Summary

This article illustrates some of the more popular issues that developers face when they try to debug their applications. It highlights best practices on how to handle error reporting and prepare your C# integration with Backtrace. By using these, you can make your code much more efficient, clean and safe. Check out more information about the Backtrace C# reporting library, and get started with your trial instance of Backtrace today!

By | 2019-04-30T03:04:01+00:00 April 15th, 2019|Backtrace, Engineering, Technical Details|