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. After one year, we would 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. This caused that an exception information was available on a standard output. 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 to environment stack frames. Later we will recognize that in most cases, stack frames are not so useful for each developer. 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 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 stack frame to existing stack trace. Please keep in mind that by removing stack frames and customizing modification you can change the way how the deduplication algorithm works. For other exceptions we suggest using global exception handler that could 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 filters (we will talk about this feature later….) or we can try to learn about exception by using reflection. How we can get exception type (classifier) from exception object in 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 reflection available property, allows us to generate correct type of a report. You can imagine how we can use an exception to generate an exception typeinformation, 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 specific catch block. In my case I prefer to filter exception by method that returns true/false for a specific exception type. How can I use exception filters in my application? This is simple – let me prove this by modifying our initial sample code.

Our exception code from handling exception type Exception we can change to:

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 exception container. What you must do to check exceptions? AggregateException has 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? 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 application to the production we could have had a much harder time than now – why? When you share your application with special .pdb files, release mode of your application will not generate stack trace with the line number information.  

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

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

If you still need a better way to find what went wrong, we have prepared for you a minidump support – application status information when application receives error. What you can do with this file? You can open minidump with your application in Visual Studio and check:

  • Where your code throw exception?
  • What was the value of your variables in 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-15T15:53:51+00:00 April 15th, 2019|Backtrace, Engineering, Technical Details|