Filter
handlers are the second type of exception handler that aren’t accessible from C#. Unlike the other handler types, which have defined conditions for when the handlers execute, filter
lets you use custom logic to determine whether the handler should be run. However, similar to a catch
block, the filter
block does not get run if control flow exits the block without throwing an exception.
Introducing filter blocks
An example of a filter block in IL is the following:
1 2 3 4 5 6 7 8 9 |
.try { // try block } filter { // filter block endfilter }{ // filter handler } |
or, in v1 syntax,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
TryStart: // try block TryEnd: FilterStart: // filter block HandlerStart: // filter handler HandlerEnd: .try TryStart to TryEnd filter FilterStart handler HandlerStart to HandlerEnd |
In the v1 syntax there is no end label specified for the filter block. This is because the filter block must come immediately before the filter handler; the end of the filter block is the start of the filter handler.
The filter block indicates to the CLR whether the filter handler should be executed using a boolean value on the stack when the endfilter
instruction is run; true/non-zero if it is to be executed, false/zero if it isn’t. At the start of the filter block, and the corresponding filter handler, a reference to the exception thrown is pushed onto the stack as a raw object
(you have to manually cast to System.Exception
).
The allowed IL inside a filter block is tightly controlled; you aren’t allowed branches outside the block, rethrow
instructions, and other exception handling clauses. You can, however, use call
and callvirt
instructions to call other methods.
Filter block logic
To demonstrate filter block logic, in this example I’m filtering on whether there’s a particular key in the Data
dictionary of the thrown exception:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
.try { // try block } filter { // Filter starts with exception object on stack // C# code: ((Exception)e).Data.Contains("MyExceptionDataKey") // only execute handler if Contains returns true castclass [mscorlib]System.Exception callvirt instance class [mscorlib]System.Collections.IDictionary [mscorlib]System.Exception::get_Data() ldstr "MyExceptionDataKey" callvirt instance bool [mscorlib]System.Collections.IDictionary::Contains(object) endfilter }{ // filter handler // Also starts off with exception object on stack callvirt instance string [mscorlib]System.Object::ToString() call void [mscorlib]System.Console::WriteLine(string) } |
Conclusion
Filter exception handlers are another exception handler type that isn’t accessible from C#, however, just like fault
handlers, the behaviour can be replicated using a normal catch block:
1 2 3 4 5 6 7 8 |
try { // try block } catch (Exception e) { if (!FilterLogic(e)) throw; // handler logic } |
So, it’s not that great a loss, but it’s still annoying that this functionality isn’t directly accessible. Well, every feature starts off with minus 100 points, so it’s understandable why something like this didn’t make it into the C# compiler ahead of a different feature.
Load comments