📜 ⬆️ ⬇️

Repeated generation of exceptions

Exception handling has appeared in mainstream programming languages ​​for more than three decades ago, but today you can still find developers who are afraid to use them. Some believe that the generation of exceptions in the designer will damage their fragile karma and they will be overtaken by a punishment in the form of supporting a code of twenty years ago written by a herd of insane Indians. Some are still stuck in the era of the C language and even in C # they intensively use return codes in the form of magic numbers or even strings, believing that cowards invented exceptions, and real samurai can do without them. (Although we all know that the real samurai follow the “Samurai principle” and do not use any return codes).

Additional complexity is added by specific platforms and programming languages. Today at the C # developer's interview, when it comes to handling exceptions, the question will surely be heard: “What is the difference between throwing exceptions using throw constructs ; and throw ex; ? And although this is a terrible button accordion and most developers have long known the correct answer to this question, in the real code it’s very easy to meet the “non-kosher” version.

A feature of the .NET platform is that it does not exist (or rather, as we will soon see - did not exist) a way to catch an exception in one place and then generate it in another. If the developer of a business application or library faced this problem, then it was solved in a very simple way: the original exception was wrapped in another object as a nested exception and a new exception was forwarded.
')


Let's look at this example. Suppose we have a custom exception class called CustomException , as well as a simple class SampleClass , whose constructor throws this exception.

//   ,  ,   class CustomException : Exception { } class SampleClass { //   , //     ,    public SampleClass() { throw new CustomException(); } } 


Now let's create an object of this class using the generic method, and also make a singleton of this class:

 //   ,    public static T CreateInstance<T>() where T : new() { return new T(); } //    class SampleClassSingleton { private static SampleClass _instance = new SampleClass(); static SampleClassSingleton() { } public static SampleClass Instance { get { return _instance; } } } 


The question is what kind of catch block will be executed when calling the CreateInstance < SampleClass> () method or when accessing SampleClassSingleton. Instnace ?

 try { CreateInstance<SampleClass>(); //  var instance = SampleClassSingleton.Instance; } catch (CustomException e) { Console.WriteLine(e); } catch (Exception e) { Console.WriteLine(e); } 


I don’t think that this code will be a great revelation for someone, but in both cases the “expected” catch block (CustomException e) will not be executed, the catch block (Exception e) will be executed instead.

In the first case (that is, when using the generic CreateInstance method), in fact, the default constructor is not called directly, instead it uses Activator.CreateInstance , which wraps the original exception in a TargetInvocationException . That is why the catch (Exception e) block is triggered, since a TargetInvocationException doesn’t suit our first exception block.

A WARNING
Do not write only e.Message in the logs, because when your production server lies down from this information you will be neither cold nor hot, since the most valuable information may be hidden in one of the nested exceptions. Even as part of the .Net Framework, there are many places that wrap the original exception and throw it as a nested one, not to mention custom code that can wrap the exception you need in a dozen nested ones.

When using singleton, the situation is similar: since the initialization of the _instance field occurs in the static constructor, any exceptions that occur during its execution make the type "invalid." As a result, all subsequent calls to this type result in a TypeLoadException exception with a corresponding nested exception.

NOTE For more information on what an empty static constructor is needed for and what problems its absence can cause, see About singletons and static constructors .

Behavior in both cases is well documented and well-known, so if once it could be considered a bug, now this is already a feature. But we cannot correct this situation, unfortunately. Even if we catch the exception in the CreateInstance method and try to forward the nested exception, we will lose the call stack, which will make the resulting exception much less useful:

 public static T CreateInstance<T>() where T : new() { try { return new T(); } catch (TargetInvocationException e) { //    ,    ,   ! throw e.InnerException; } } 


A similar problem awaits us if we try to save an exception when creating an instance of _ instance and will forward it when accessing the Instance property. (Yes, deferred initialization will solve this particular problem, but now this is not the point).

However, a class appeared in the .Net Framework 4.5 that can help solve this problem. This is the ExceptionDispatchInfo class that can save the original exception and then re-forward it without losing information about the call stack. Of course, the basic meaning of its use is not related to the “straightening” of static constructors or the Activator.CreateInstance method. Its main task is to address issues of asynchrony and multithreading, when an exception occurs in one stream, and is forwarded in another.

Let's look at the following code:

 Task<int> task = Task<int>.Factory.StartNew(() => { throw new CustomException(); }); try { int result = task.Result; } catch (CustomException e) { // ,      :( Console.WriteLine("CustomException caught: " + e); } catch (AggregateException e) { //      ! var inner = e.GetBaseException(); Console.WriteLine("Aggregate excpetion caught: " + inner); } 


If the “task” falls with an exception, then the original exception (in our case, CustomException ) will be wrapped in an AggregateException . There are several reasons for this behavior: first, although the task is by its nature a shell over some long-term asynchronous operation with a single result, in some cases the result of one task may be based on the results of parallel execution of several tasks. For example, we can combine several tasks into one using Task . WaitAll or we can link several tasks with continuations. The second reason for this behavior is the impossibility of forwarding the original exception without distorting its call stack.

However, after the appearance of new features in working with asynchrony in C # 5.0, priorities have changed somewhat. One of the main features of the await and async keywords is the simplicity of converting synchronous code into asynchronous, but in addition to “straightening” the flow of execution (which even in the case of application of tasks leaves much to be desired), the problem of exception handling must also be solved. Therefore, when getting the results of a task using the await keyword, the original AggregateException is expanded and the original exception is forwarded:

 public static async void SimpleTask() { Task<int> task = Task<int>.Factory.StartNew(() => { throw new CustomException(); }); try { // await «»      //    ,   AggregateExcpetion! int result = await task; } catch (CustomException e) { //    ,       Console.WriteLine("CustomException caught: " + e); } } 


NOTE
If you are not familiar with such features of the C # 5.0 language as async and await , then you can fill this gap with the help of the article: Asynchronous operations in C # 5

As already mentioned, this behavior is implemented using the new ExceptionDispatchInfo class, which allows you to save the original exception and forward it later, perhaps even in a new thread. Moreover, it is implemented so simply that it is not difficult for us to do it on our own.

To begin with, let's create a class for the T ask < T > class with the GetResult extension method , which will be very similar to the Result property, but will “straighten” AggregateExcpetion and forward an nested exception without losing the call stack.

NOTE
This behavior has already been implemented as part of .Net Framework 4.5 using Task < T >. GetAwaiter (). GetResult () , but let's forget about it and do the same thing yourself.

Using the ExceptionDispatchInfo class is quite simple: to do this, it is enough to capture the exception in one place using the static method Capture , and then forward the exception in another place (and possibly even in another thread) using the Throw method.

 static class TaskExtensions { public static T GetResult<T>(this Task<T> task) { try { T result = task.Result; return result; } catch (AggregateException e) { ExceptionDispatchInfo di = ExceptionDispatchInfo.Capture(e.InnerException); di.Throw(); return default(T); } } } 


Now, if we change the previous code snippet and replace the task . Result to call the Task method . GetResult , then we will be able to intercept a specific exception type instead of an AggregateException exception.

 Task<int> task = Task<int>.Factory.StartNew(() => { throw new CustomException(); }); try { int result = task.GetResult(); } catch (CustomException e) { //     CustomException,   AggregateException Console.WriteLine("CustomException caught: " + e); } 


Similarly, you can change our CreateInstance method, which will catch the TargetInfocationException exception and throw a nested exception:

 public static T CreateInstance<T>() where T : new() { try { var t = new T(); return t; } catch (TargetInvocationException e) { //     ExceptionDispatchInfo ExceptionDispatchInfo di = ExceptionDispatchInfo.Capture(e.InnerException); //        di.Throw(); //   ,  di.Throws()   ,  //        ,     //   return default(T); } } 


Now, when we try to call this method from the Main function, we will get a sensible exception with the normal execution stack:

ConsoleApplication1.CustomException: Exception of type 'ConsoleApplication1.CustomException' was thrown.<br>at ConsoleApplication1.SampleClass..ctor() in <br>c:\Projects\ConsoleApplication1\ConsoleApplication1\Program.cs:line 19<br> --- End of stack trace from previous location where exception was thrown ---<br> at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()<br>at ConsoleApplication1.Program.CreateInstance[T]() in c<br>\Projects\ConsoleApplication1\ConsoleApplication1\Program.cs:<br>line 50<br>at ConsoleApplication1.Program.Main(String[] args) in c:\ \Projects\ConsoleApplication1\ConsoleApplication1\Program.cs:line 63


The singleton class can be implemented in a similar way.

The ExceptionDispatchInfo class is unlikely to have a killer feature of the .Net Framework version, but with the onset of the asynchrony era, it will definitely have a decent scope. So, for example, it is already used in the library of reactive extensions to implement the await pattern (this implementation is available only in the experimental release) and can be used by anyone who wants to implement this pattern on their own.

Source: https://habr.com/ru/post/131811/


All Articles