In my blog posts on fault and filter exception handlers, I said that the same behaviour could be replicated using normal catch blocks. Well, that isn't entirely true...
Changing the handler semantics
Consider the following:
.try {
.try {
.try {
newobj instance void [mscorlib]System.Exception::.ctor()
// IL for:
// e.Data.Add("DictKey", true)
throw
}
fault {
ldstr "1: Fault handler"
call void [mscorlib]System.Console::WriteLine(string)
endfault
}
}
filter {
ldstr "2a: Filter logic"
call void [mscorlib]System.Console::WriteLine(string)
// IL for:
// (bool)((Exception)e).Data["DictKey"]
endfilter
}{
ldstr "2b: Filter handler"
call void [mscorlib]System.Console::WriteLine(string)
leave.s Return
}
}
catch object {
ldstr "3: Catch handler"
call void [mscorlib]System.Console::WriteLine(string)
leave.s Return
}
Return:
// rest of method
If the filter handler is engaged (true is inserted into the exception dictionary) then the filter handler gets engaged, and the following gets printed to the console:
2a: Filter logic
1: Fault handler
2b: Filter handler
and if the filter handler isn't engaged, then the following is printed:
2a:Filter logic
1: Fault handler
3: Catch handler
Filter handler execution
The filter handler is executed first. Hmm, ok. Well, what happens if we replaced the fault block with the C# equivalent (with the exception dictionary value set to false)?
.try {
// throw exception
}
catch object {
ldstr "1: Fault handler"
call void [mscorlib]System.Console::WriteLine(string)
rethrow
}
we get this:
1: Fault handler
2a: Filter logic
3: Catch handler
The fault handler is executed first, instead of the filter block.
Eh?
This change in behaviour is due to the way the CLR searches for exception handlers. When an exception is thrown, the CLR stops execution of the thread, and searches up the stack for an exception handler that can handle the exception and stop it propagating further - catch or filter handlers. It checks the type clause of catch clauses, and executes the code in filter blocks to see if the filter can handle the exception.
When the CLR finds a valid handler, it saves the handler's location, then goes back to where the exception was thrown and executes fault and finally blocks between there and the handler location, discarding stack frames in the process, until it reaches the handler.
So?
By replacing a fault with a catch, we have changed the semantics of when the filter code is executed; by using a rethrow instruction, we've split up the exception handler search into two - one search to find the first catch, then a second when the rethrow instruction is encountered.
This is only really obvious when mixing C# exception handlers with fault or filter handlers, so this doesn't affect code written only in C#. However it could cause some subtle and hard-to-debug effects with object initialization and ordering when using and calling code written in a language that can compile fault and filter handlers.