Exception filters have been around for some time. Visual Basic.NET (VB.NET) and F# devs have had this functionality for a while. Luckily for us, it has now been introduced in C# 6.0. Exception filters do more than what meets the eye. At first glance, it looks as if exception filters merely specify a condition when an exception needs to be caught. This is, after all, what the name "exception filter" implies. Upon closer inspection, however, we see that exception filters act as more than just syntactical sugar.
We will create a new class called Recipe8ExceptionFilters
and call a method that reads an XML file. The file read logic is determined by a Boolean flag being set to true
. Imagine here that there is some other database flag that when set, also sets our Boolean flag to true
, and thus, our application knows to read the given XML file.
Recipe8ExceptionFilters
that contains two methods. One method reads the XML file, and the second method logs any exception errors:public static class Recipe8ExceptionFilters { public static void ReadXMLFile(string fileName) { try { bool blnReadFileFlag = true; if (blnReadFileFlag) { File.ReadAllLines(fileName); } } catch (Exception ex) { Log(ex); throw; } } private static void Log(Exception e) { /* Log the error */ } }
ReadXMLFile
method, passing it the file name to read:string File = @"c: empXmlFile.xml"; Chapter1.Recipe8ExceptionFilters.ReadXMLFile(File);
If we had to run our application now, we would obviously receive an error (this is assuming that you actually don't have a file called XMLFile.xml
in your temp
folder). Visual Studio will break on the throw
statement:
The Log(ex)
method has logged the exception, but have a look at the Watch1 window. We have no idea what the value of blnReadFileFlag
is. When an exception is caught, the stack is unwound (adding overhead to your code) to whatever the actual catch block is. Therefore, the state of the stack before the exception happened is lost. Modify your ReadXMLFile
and Log
methods as follows to include an exception filter:
public static void ReadXMLFile(string fileName) { try { bool blnReadFileFlag = true; if (blnReadFileFlag) { File.ReadAllLines(fileName); } } catch (Exception ex) when (Log(ex)) { } } private static bool Log(Exception e) { /* Log the error */ return false; }
When you run your console application again, Visual Studio will break on the actual line of code that caused the exception:
More importantly, the value of blnReadFileFlag
is still in scope. This is because exception filters can see the state of the stack at the point where the exception occurred instead of where the exception was handled. Looking at the Locals window in Visual Studio, you will see that the variables are still in scope at the point where the exception occurred:
Imagine being able to view the exception information in a log file with all the local variable values available. Another interesting point to note is the return false
statement in the Log(ex)
method. Using this method to log the error and return false
will allow the application to continue and have the exception handled elsewhere. As you know, catching Exception ex
will catch everything. By returning false
, the exception filter doesn't run into the catch
statement, and more specific catch
exceptions (for example, catch (FileNotFoundException ex)
after our catch (Exception ex)
statement) can be used to handle specific errors. Normally, when catching exceptions, FileNotFoundException
will never be caught in the following code example:
catch (Exception ex) { } catch (FileNotFoundException ex) { }
This is because the order of the exceptions being caught is wrong. Traditionally, developers must catch exceptions in their order of specificity, which means that FileNotFoundException
is more specific than Exception
and must therefore be placed before catch (Exception ex)
. With exception filters that call a false returning method, we can inspect and log an exception accurately:
catch (Exception ex) when (Log(ex)) { } catch (FileNotFoundException ex) { }
The preceding code will catch all exceptions, and in doing so log the exception accurately but not step into the exception handler because the Log(ex)
method returns false
.
Another implementation of exception filters is that they can allow developers to retry code in the event of a failure. You might not specifically want to catch the first exception, but implement a type of timeout element to your method. When the error counter has reached the maximum iterations, you can catch and handle the exception. You can see an example of catching an exception based on a try
clauses' count here:
public static void TryReadXMLFile(string fileName) { bool blnFileRead = false; do { int iTryCount = 0; try { bool blnReadFileFlag = true; if (blnReadFileFlag) File.ReadAllLines(fileName); } catch (Exception ex) when (RetryRead(ex, iTryCount++) == true) { } } while (!blnFileRead); } private static bool RetryRead(Exception e, int tryCount) { bool blnThrowEx = tryCount <= 10 ? blnThrowEx = false : blnThrowEx = true; /* Log the error if blnThrowEx = false */ return blnThrowEx; }
Exception filtering is a very useful and extremely powerful way to handle exceptions in your code. The behind-the-scenes workings of exception filters are not as immediately obvious as one might imagine, but here lies the actual power of exception filters.
3.138.35.193