Exception filters C# with Example



Exception filters C# with Example

Exception filters give developers the ability to add a condition (in the form of a boolean expression) to a catch block, 
allowing the catch to execute only if the condition evaluates to true. 
Exception filters allow the propagation of debug information in the original exception, where as using an if 
statement inside a catch block and re-throwing the exception stops the propagation of debug information in the 
original exception. With exception filters, the exception continues to propagate upwards in the call stack unless the 
condition is met. As a result, exception filters make the debugging experience much easier. Instead of stopping on 
the throw statement, the debugger will stop on the statement throwing the exception, with the current state and all 
local variables preserved. Crash dumps are affected in a similar way. 
Exception filters have been supported by the CLR since the beginning and they've been accessible from 
VB.NET and F# for over a decade by exposing a part of the CLR's exception handling model. Only after the 
release of C# 6.0 has the functionality also been available for C# developers. 
Using exception filters 
Exception filters are utilized by appending a when clause to the catch expression. It is possible to use any 
expression returning a bool in a when clause (except await). The declared Exception variable ex is accessible from 
within the when clause: 
var SqlErrorToIgnore = 123; 
try 
{ 
DoSQLOperations(); 
} 
catch (SqlException ex) when (ex.Number != SqlErrorToIgnore) 
{ 
throw new Exception("An error occurred accessing the database", ex); 
} 
Multiple catch blocks with when clauses may be combined. The first when clause returning true will cause the 
exception to be caught. Its catch block will be entered, while the other catch clauses will be ignored (their when 
clauses won't be evaluated). For example: 
try 
{ ... } 
catch (Exception ex) when (someCondition) //If someCondition evaluates to true, 
//the rest of the catches are ignored. 
{ ... } 
catch (NotImplementedException ex) when (someMethod()) //someMethod() will only run if 
//someCondition evaluates to false 
{ ... } 
 

catch(Exception ex) // If both when clauses evaluate to false 
{ ... } 
Risky when clause 
Caution 
It can be risky to use exception filters: when an Exception is thrown from within the when clause, the 
Exception from the when clause is ignored and is treated as false. This approach allows developers to 
write when clause without taking care of invalid cases. 
The following example illustrates such a scenario: 
public static void Main() 
{ 
int a = 7; 
int b = 0; 
try 
{ 
DoSomethingThatMightFail(); 
} 
catch (Exception ex) when (a / b == 0) 
{ 
// This block is never reached because a / b throws an ignored 
// DivideByZeroException which is treated as false. 
} 
catch (Exception ex) 
{ 
// This block is reached since the DivideByZeroException in the 
// previous when clause is ignored. 
} 
} 
public static void DoSomethingThatMightFail() 
{ 
// This will always throw an ArgumentNullException. 
Type.GetType(null); 
} 
View Demo 
Note that exception filters avoid the confusing line number problems associated with using throw when failing code 
is within the same function. For example in this case the line number is reported as 6 instead of 3: 
1. int a = 0, b = 0; 
2. try { 
3. int c = a / b; 
4. } 
5. catch (DivideByZeroException) { 
6. throw; 
7. } 
The exception line number is reported as 6 because the error was caught and re-thrown with the throw statement 
on line 6. 
The same does not happen with exception filters: 
 

1. int a = 0, b = 0; 
2. try { 
3. int c = a / b; 
4. } 
5. catch (DivideByZeroException) when (a != 0) { 
6. throw; 
7. } 
In this example a is 0 then catch clause is ignored but 3 is reported as line number. This is because they do not 
0 
unwind the stack. More specifically, the exception is not caught on line 5 because a in fact does equal and thus 
there is no opportunity for the exception to be re-thrown on line 6 because line 6 does not execute. 
Logging as a side effect 
Method calls in the condition can cause side effects, so exception filters can be used to run code on exceptions 
without catching them. A common example that takes advantage of this is a Log method that always returns false. 
This allows tracing log information while debugging without the need to re-throw the exception. 
Be aware that while this seems to be a comfortable way of logging, it can be risky, especially if 3rd party 
logging assemblies are used. These might throw exceptions while logging in non-obvious situations that 
may not be detected easily (see Risky when(...) clause above). 
try 
{ 
DoSomethingThatMightFail(s); 
} 
catch (Exception ex) 
(Log(ex, "An error occurred")) { // This catch block will never be reached } // ... static bool Log(Exception ex, string 
message, params object[] args) { Debug.Print(message, args); return false; } 
View Demo 
The common approach in previous versions of C# was to log and re-throw the exception. 
Version < 6.0 
try 
{ 
DoSomethingThatMightFail(s); 
} 
catch (Exception ex) 
{ 
Log(ex, "An error occurred"); 
throw; 
} 
// ... 
static void Log(Exception ex, string message, params object[] args) 
{ 
Debug.Print(message, args); 
} 
View Demo 
 

The finally block 
The finally block executes every time whether the exception is thrown or not. One subtlety with expressions in 
when is exception filters are executed further up the stack before entering the inner finally blocks. This can cause 
unexpected results and behaviors when code attempts to modify global state (like the current thread's user or 
culture) and set it back in a finally block. 
Example: finally block 
private static bool Flag = false; 
static void Main(string[] args) 
{ 
Console.WriteLine("Start"); 
try 
{ 
SomeOperation(); 
} 
catch (Exception) when (EvaluatesTo()) 
{ 
Console.WriteLine("Catch"); 
} 
finally 
{ 
Console.WriteLine("Outer Finally"); 
} 
} 
private static bool EvaluatesTo() 
{ 
Console.WriteLine($"EvaluatesTo: {Flag}"); 
return true; 
} 
private static void SomeOperation() 
{ 
try 
{ 
Flag = true; 
throw new Exception("Boom"); 
} 
finally 
{ 
Flag = false; 
Console.WriteLine("Inner Finally"); 
} 
} 
Produced Output: 
Start 
EvaluatesTo: True 
Inner Finally 
Catch 
Outer Finally 
View Demo 
 

In the example above, if the method SomeOperation does not wish to "leak" the global state changes to caller's when 
clauses, it should also contain a catch block to modify the state. For example: 
private static void SomeOperation() 
{ 
try 
{ 
Flag = true; 
throw new Exception("Boom"); 
} 
catch 
{ 
Flag = false; 
throw; 
} 
finally 
{ 
Flag = false; 
Console.WriteLine("Inner Finally"); 
} 
} 
It is also common to see IDisposable helper classes leveraging the semantics of using blocks to achieve the same 
goal, as IDisposable.Dispose will always be called before an exception called within a using block starts bubbling 
up the stack. 

0 Comment's

Comment Form

Submit Comment