Subterranean IL: Fault exception handlers
- by Simon Cooper
Fault event handlers are one of the two handler types that aren't available in C#. It behaves exactly like a finally, except it is only run if control flow exits the block due to an exception being thrown.
As an example, take the following method:
.method public static void FaultExample(bool throwException) {
.try {
ldstr "Entering try block"
call void [mscorlib]System.Console::WriteLine(string)
ldarg.0
brfalse.s NormalReturn
ThrowException:
ldstr "Throwing exception"
call void [mscorlib]System.Console::WriteLine(string)
newobj void [mscorlib]System.Exception::.ctor()
throw
NormalReturn:
ldstr "Leaving try block"
call void [mscorlib]System.Console::WriteLine(string)
leave.s Return
}
fault {
ldstr "Fault handler"
call void [mscorlib]System.Console::WriteLine(string)
endfault
}
Return:
ldstr "Returning from method"
call void [mscorlib]System.Console::WriteLine(string)
ret
}
If we pass true to this method the following gets printed:
Entering try block
Throwing exception
Fault handler
and the exception gets passed up the call stack. So, the exception gets thrown, the fault handler gets run, and the exception propagates up the stack afterwards in the normal way.
If we pass false, we get the following:
Entering try block
Leaving try block
Returning from method
Because we are leaving the .try using a leave.s instruction, and not throwing an exception, the fault handler does not get called.
Fault handlers and C#
So why were these not included in C#? It seems a pretty simple feature; one extra keyword that compiles in exactly the same way, and with the same semantics, as a finally handler. If you think about it, the same behaviour can be replicated using a normal catch block:
try {
throw new Exception();
}
catch {
// fault code goes here
throw;
}
The catch block only gets run if an exception is thrown, and the exception gets rethrown and propagates up the call stack afterwards; exactly like a fault block. The only complications that occur is when you want to add a fault handler to a try block with existing catch handlers. Then, you either have to wrap the try in another try:
try {
try {
// ...
}
catch (DirectoryNotFoundException) {
// ...
// leave.s as normal...
}
catch (IOException) {
// ...
throw;
}
}
catch {
// fault logic
throw;
}
or separate out the fault logic into another method and call that from the appropriate handlers:
try {
// ...
}
catch (DirectoryNotFoundException ) {
// ...
}
catch (IOException ioe) {
// ...
HandleFaultLogic();
throw;
}
catch (Exception e) {
HandleFaultLogic();
throw;
}
To be fair, the number of times that I would have found a fault handler useful is minimal. Still, it's quite annoying knowing such functionality exists, but you're not able to access it from C#. Fortunately, there are some easy workarounds one can use instead.
Next time: filter handlers.