Subterranean IL: Filter exception handlers
- by Simon Cooper
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:
.try {
// try block
}
filter {
// filter block
endfilter
}{
// filter handler
}
or, in v1 syntax,
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:
.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:
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.