This solution is supposed to be a starting point. Since its implementation is simple, it can easily be altered to suit your needs.

Introduction

This article provides a method (and implementation) to easily toggle the way exceptions are being handled (either being thrown or handled by custom code), while still conserving the stack trace when exceptions are not being thrown.

This way, possible bugs are caught early, and are easy to resolve due to the data in the exception (exception type, message and stack trace).

This sounds great! But let's enter the real world...

Imagine working on a product with several developers using the method above. The product is starting to get larger and consists of multiple components. One day, you, or one of your colleagues has been a bit careless and committed some code without testing it properly. No problem: an exception pops up! However, this exception now pops up for everyone that is working on the product. Even developers working on an entirely different component will be blocked until this one exception is solved.

Of course, this is a very crude example, and exception handling can be done per component or class. But from my experience it can be very useful to toggle the way exceptions are handled depending on the people using it.

This article is about just that: toggling whether exceptions should be thrown, or handled in a different way.

So...why not just log?

When I throw an exception, I do so because I cannot guarantee the stability of the program when a certain execution path occurs.

However, in projects where a team wants to avoid an application to crash on a single exception (see example in the chapter above), these exceptions might be caught on multiple locations (early on) and logged, or these kind of exceptions are replaced by logging altogether.

Replacing exceptions by logging doesn't have to be a bad thing. But in some cases, the application might not show any symptoms or problems when such an "exception" occurs. If this happens a lot, log files will be full of exceptions over time, making it increasingly difficult to find real problems.

On top of the above, logged (/ignored) "exceptions" might cause a program to become unstable over time. If the log is flooded with mundane exceptions as well, it will be very difficult to find the original cause of a bug caused this way.

But...are exceptions (and stack traces) not slow?

Throwing exceptions and creating call stacks can be relatively slow. But that's why they should only be used in cases of...exception. The method I'm offering here doesn't mean you should turn of the throwing of exceptions altogether and ignore everything that is happening in your code as long as it still seems to work.

The purpose of this article/solution is to give developers the means to toggle between throwing exceptions and handling them in an alternative way, mainly during the development process. Every exception that occurs in the (released) code is still one to many,

Using the code

Throwing/Creating Exceptions

Instead of throwing exceptions, exceptions will go through a class called the ExceptionHandler. This class determines whether exceptions are thrown or should be handled by custom code.

To prevent having to retrieve an ExceptionHandler object everywhere, the included library makes use of an extension method on the Exception class. Of course you are free to implement this part however you see fit.

When the method has a return type, it's up to the developer what the method should return in an exceptional case. To make things easy, the Handle<TReturn> method allows the developer to provide a return value.

When using exceptions as shown above, the way the developer wants to handle them can be manipulated in the ExceptionHandler class.

The ExceptionHandler is the subject of a basic observer pattern that allows observers of type IUnthrownExceptionHandler to register to it. If the property ThrowExceptions is set to false, the Handle method on all registered handlers is called as soon as the exception is passed to the ExceptionHandler.

When an exception is not thrown by the ExceptionHandler, an UnthrownException is created for it. The UnthrownException class has the following properties:

Exception - The original exception class.

StackTrace - The stack trace of the exception, as a string.

Note: When catching an exception, the stack trace is shortened to contain the trace from the location it is caught to the location the exception is thrown. Because the exception is handled down the stack, StackTrace will contain the complete stack until the Handle method was called.

Origin - An instance of the ExceptionOrigin class, containing information about the origin of the exception.

The properties of ExceptionOrigin:

CalledType - The type of the class that created the exception.

Method - The name of the method in which the exception was created.

Line - The line number in the file in which the exception was created.

Column - The column number of the file in which the exception was created.

An example of using the ExceptionOrigin is to filter certain exceptions, or to use it to group reoccurring exceptions.

How should I handle exceptions?

This is up to you. The solution I created is just a tool to make it possible.

But as exceptions probably indicate a serious problem, they should not be overlooked. You might want to log them to a dedicated exception log. And in case of a released product, it might also be useful to send a mail to the developers in case an exception occurs.

When to handle and when to throw?

Again, this is up to you. But some examples might be:

Throwing

On develop machines - Don't let developers get away with exceptions! In case of the example above, just temporarily disable them by setting ThrowExceptions to false.

On test machines - During testing, it is very important to find any exception.

Handling

Released product - This of course differs from situation to situation, but an example could be writing handlers that throw exceptions in critical components, but just log exceptions from less important components.

Demo - It might be useful to disable exceptions when giving a demo. Having one thing disappear from the screen might be preferable to an error pop-up to some stakeholders.

Points of Interest

The solution is supposed to be a starting point. Since its implementation is simple, it can easily be altered to suit your needs.

By default, the ExceptionHandler uses a singleton pattern. You are of course free to ignore it.

If an exception is not thrown, finally blocks are being executed after an exception is handled. I've thought of adding an optional Action to the Handle method, but I've not had the need for this so far.

There are three reasons why I chose to pass an Exception class to the Handle methods (instead of just writing a message there, and creating an exception when needed):

I wanted to change as little as possible on the user side. Now, only throw has to be replaced with the Handle call.

It is now still very visible that a certain execution path is an exception.

The type of Exception class contains information about the exception as well. When dealing with a NullReferenceException, you know what to look for. It also makes it easier to look for usages of certain exceptions.

I am not particularly happy with the name ExceptionHandler since it might be confusing in combination with IUnthrownExceptionHandler. I am open for suggestions.

History

21-12-2017 - Version 1

02-01-2018 - Version 1.1

Re-throwing the exception in ExceptionExt in order to minimize the part of the stack trace created by the ExceptionHandling library.

03-01-2018 - Version 1.2

The user can not specify a return value in the Handle method.

The Handle method in ExceptionExt will not re-throw exceptions anymore, as it can hide exceptions thrown by custom implementations of IUnthrownExceptionHandler. Instead, exceptions are now thrown immediately if this is desired, also minimizing the stack trace as it will not include the ExceptionHandling class.

License

Share

About the Author

Jasper is part of an amazing development team at Vanderlande, where he is a software developer working with C#. His main focus when coding is quality and readability, and he is experienced in a broad range of subjects (Software design, testing, UI, simulation, multithreading, networking and more).

His passion for programming can be traced back to his pre-professional days. Where, even as an elementary school student he could be found on the computer creating computer games. The reason? There is just no feeling like being able to think something up, create it, and then see others enjoy it.

Outside the office, he's a contributor to the Code Project and there is always a project he's working on. When he's not coding he likes to make and edit video’s, can discuss theoretical physics for hours and if you challenge him to a board game, he won’t say no. He can also frequently be found in the gym and travels when he can.

Comments and Discussions

Hi.
Just an opinion.
I think you should be careful to hide the exceptions. There is a reason you are throwing them. But I think you have already understood this.
It is better to fix the problem that is causing the exception right away. Otherwise you might get surprised on more complex problems that takes longer time to solve later on.
If someone else is using you code and get the exception they are probably doing something wrong, right? Maybe you could solve that problem together if you are in the same team. Often an older developer, like me, can learn a lot from younger developers.
There is a risk that this solution will make your team sweep the dirt under the carpet.
/Kristian

However, depending on the size of the team, and way of working, there are people that might not. This solution is provided simply as a tool for those who need it. It might help a team choose for exceptions where it would normally choose logging instead. (I can only hope)

If it were only up to me, I would use the normal way of throwing exceptions wherever possible.

The default return value is just a tool in case developers would like to use it. If it doesn't suit their needs, they can use the example above.

On your second point I tend to agree. The name "Handle" doesn't really cover what is actually happening, as the exception can also be thrown depending on the settings you're using. I am open for suggestions!
The point of the handle method is to give the user the opportunity to do something different with an exception if desired. How and when it's used, is again up to the developer. If the custom code throws an exception of itself, the ExceptionExt method does hide the stack trace. For me it was a trade-of of hiding this stack, or showing the stack of the ExceptionHandler in all cases. I chose the first.
Come to think of it, I might make this optional in the extension methods. But as stated in the article, the extension method is only to make things easier. The developer can also use the ExceptionHandler class directly, in which case this is no problem.

I think allowing the user to pass a parameter is a good idea, I've updated the article.

The re-throwing of exceptions is also removed, as it could hide exceptions from custom code. Instead, I simply throw the exception right away if desired. If not, only the handle method of ExceptionHandler is called:

public static TReturn Handle<TReturn>(this Exception exception, TReturn returnValue)
{
if (ExceptionHandler.Instance.ThrowExceptions)
{
// We throw the exception here in order to minimize the part of the stack trace created by the ExceptionHandling library.
throw exception;
}
return ExceptionHandler.Instance.Handle(exception, returnValue);
}

AOP was unfamiliar to me, but looks interesting. I will look into this, and might update this article in the future. Thanks for the tip!

The attached demo application is a Windows Console Application.
The article itself was inspired by the same type of application.

I have less experience with web applications, but I would imagine API calls to always throw an exception, as they are triggered by a third party. Internal workings of such an application could still use this solution if desired.