📜 ⬆️ ⬇️

Unexpected exception filter behavior in C # 6

What are exception filters?


Exception Filters is a new C # 6 feature that allows you to set specific conditions for a catch . This block will be executed only if the specified conditions are met. We illustrate the syntax with a small code snippet:

 public void Main() { try { throw new Exception("E2"); } catch(Exception ex) when(ex.Message == "E1") { Console.WriteLine("caught E1"); } catch(Exception ex) when(ex.Message == "E2") { Console.WriteLine("caught E2"); } } 

Is this really a new feature?


For C #, yes. However, support for exception filters has long been present in IL and VB.NET. Even F # supports these filters through a mechanism called exception pattern matching.

Can we get this functionality with the help of ordinary conditional statements?


Logically, yes, but there is a fundamental difference. If the condition is inside a catch block, the exception will be caught first, and then the condition will be checked. Exception filters check the condition before an exception is caught. If the condition is not met, then the catch block will be skipped, .NET will proceed to consider the next catch block.
')

So what's the difference?


When you catch an exception, you have an unwound stack, i.e. lose important information about the exception. There is an erroneous opinion that if we execute a throw inside a catch block instead of a throw ex , then we save the stack. The fact is that people think only about the StackTrace exception StackTrace , but not about the CLR stack itself. Let's take an example. If you try to start it yourself, then make sure that the “Break on Exception” checkbox for exceptions is turned off in the Visual Studio settings (Debug-> Exceptions-> Uncheck all).

Consider a common scenario in which we catch an exception, log it and do nothing else. The following image shows where the debugger stops when an exception is thrown. Note the stop line of the debugger and the Locals window:



Now let's rewrite the example a bit using exception filters. Let's make the Log method always return false and perform logging using exception filters instead of placing it inside the catch block. Notice again the stop line of the debugger and the debugger window ( Note: the post and example were updated, but the picture remained the same; instead of catch (if(Log())) should read catch when (Log()) ):



From the translator: the original picture is outdated, because in the recent past, the exclusion filter syntax has changed a bit: they were previously described using the if keyword, and now it has been replaced with a new when keyword. Motivation is well illustrated by the following picture:



In addition to information on where exactly the exception occurred, you can also see in the Locals window of the second example a local variable localVariable , which is not available in the first example, because it is not on the stack inside the catch block. This corresponds to what we can see in crash dumps.

In addition, if you enter a catch block, you will not enter the rest. If you analyzed the condition and decided not to throw it out again, then you will not be able to get into other catch blocks. In the case of exclusion filters, an unfulfilled condition does not prevent us from checking the remaining catch conditions to try to enter another block.

Expected behavior


Thus, we can specify a condition in the exception filter; catch block will only be executed if the condition is met. And we can use the bool function as a condition. But what happens if the condition itself throws an exception? The expected behavior is the following: the exception is ignored, the conditions are considered false. Consider the following code:

 class Program { public static void Main() { TestExceptionFilters(); } public static void TestExceptionFilters() { try { throw new Exception("Original Exception"); } catch (Exception ex) when (MyCondition()) { Console.WriteLine(ex); } } public static bool MyCondition() { throw new Exception("Condition Exception"); } } 

When an "Original Exception" exception is thrown "Original Exception" before entering a catch MyCondition condition will be checked. But this condition itself throws an exception, which should be ignored, and the condition should be considered false. Thus, we get an unhandled exception:

 System.Exception: Original Exception 

Unexpected behavior


It is time for a strange example. Let's change the above code so that instead of directly calling the TestExceptionFilters() method, this method will be invoked through reflection. The expected behavior remains the same, although we call the function differently.

 class Program { public static void Main() { var targetMethod = typeof(Program).GetMethod("TestExceptionFilters"); targetMethod.Invoke(null, new object[0]); } public static void TestExceptionFilters() { try { throw new Exception("Original exception"); } catch (Exception ex) when (MyCondition()) { Console.WriteLine(ex); } } public static bool MyCondition() { throw new Exception("Condition Exception"); } } 

Let's run this code. As expected, we will get an unhandled exception, but the exception type will be different:

 System.Exception: Condition Exception 

Thus, the type of exception depends on the way we called the function. About this bug got an issue on GitHub ( Exception filters ). At the time of writing the translation, the bug is still present in CoreCLR. Let's hope that someone will fix it soon.

From the translator: this post is an integral translation of two posts at once from the site www.volatileread.com : Unpredictable Behavior With C # 6 Exception Filters and C # 6 Exception Filters

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


All Articles