- A finally handler is executed whenever the block is exited, regardless of whether this happened by normal control flow or an unhandled exception. C# exposes this using the finally keyword.
- A type-filtered handler handles an exception of a specified class or any of its subclasses. Better known as a “catch block”, C# provides this through its catch keyword.
- A user-filtered handler runs user-specified code to determine whether the exception should be ignored, handled by the associated handler, or passed on to the next protected block. C# doesn’t expose this, but Visual Basic does by means of its When keyword.
- A fault handler is executed if an exception occurs, but not on completion of normal control flow. Neither C# nor Visual Basic provide a fault handler language feature.
bool success = false;If an exception happens during “Do stuff”, we end up in the finally block and come to conclude success was never set to true. This indicates an error happened, and we should handle the fault case. However, this technique can get a bit tricky when there are different paths exiting the try block: one could return from the enclosing method in various places, requiring the “success = true” code to be sprinkled around. This is exactly what exception handling was designed for: reducing clutter in your code that has to do with error condition/code tracking. So, we’re defeating that purpose.
try
{ // Do stuff success = true;
}
finally
{
if (!success)
{
// There was a fault. Do something special.
}
// Fault or not; this is what finally does.
}
Today’s challenge is to create a true fault handler in C#, just for the sake of it. This is merely a brain teaser, encouraging readers to find out what happens behind the scenes of compiled C# code. We won’t be addressing certain concerns like non-local return (the case I mentioned above) but will be hunting for the true “fault” handler treasure hidden deeply in the C# compiler’s IL code emitter. The operational specification is the following:
var f = Fault(() => Console.WriteLine("Okay"),The above should produce the following output:
() => Console.WriteLine("Fault"));
f();
Console.WriteLine();
var g = Fault(() => { throw new Exception("Oops"); },
() => Console.WriteLine("Fault"));
try
{
g();
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
OkayAction f illustrates the non-exceptional case where the fault handler is not invoked (a finally handler would get invoked). Action g illustrates the exceptional case where the fault handler gets invoked and the exception bubbles up to the catch-block surrounding its invocation.
Fault
System.Exception: Oops
at Program.<Main>b__2()
(I won’t reveal the secrets here yet…) at Program.Main()
It’s strictly forbidden to use local state in Fault (or a method it calls) to track the successful execution of the protected block. Therefore, the below is an invalid solution:
static Action Fault(Action protectedBlock, Action faultHandler)Moreover, execution of your Fault method should really use a fault handler as encountered in IL code. It should be a fault handler, not mimic one. In addition, you should not go for a solution where you write a Fault method in ILASM by hand and link it as a netmodule in a C# project, using al.exe:
{
return () =>
{
bool success = false;
try
{
protectedBlock();
success = true;
}
finally
{
if (!success)
faultHandler();
}
};
}
.class private FaultClosureAgain, this exercise is just for fun with no profit other than brain stimulation. Hint: what C# 2.0 or later feature may cause a “fault” block to be emitted (i.e. if you ildasm a compiled valid C# application, you can find a “fault” keyword)?
{
.field class [System.Core]System.Action protectedBlock
.field class [System.Core]System.Action faultHandler
.method void .ctor()
{
ldarg.0
call instance void [mscorlib]System.Object::.ctor()
ret
}
.method void Do()
{
.try
{
ldarg.0
ldfld class [System.Core]System.Action Program/FaultClosure::protectedBlock
callvirt instance void [System.Core]System.Action::Invoke()
leave.s END
}
fault
{
ldarg.0
ldfld class [System.Core]System.Action Program/FaultClosure::faultHandler
callvirt instance void [System.Core]System.Action::Invoke()
endfault
}
END: ret
}
}
.method static class [System.Core]System.Action Fault(class [System.Core]System.Action protectedBlock, class [System.Core]System.Action faultHandler)
{
.locals init (class Program/FaultClosure V_0)
newobj void Program/FaultClosure::.ctor() stloc.0
ldloc.0
ldarg.0
stfld class [System.Core]System.Action Program/FaultClosure::protectedBlock ldloc.0
ldarg.1
stfld class [System.Core]System.Action Program/FaultClosure::faultHandler
ldloc.0
ldftn instance void Program/FaultClosure::Do()
newobj void [System.Core]System.Action::.ctor(object, native int)
ret
}
enjjjjjjjjjj........................
No comments:
Post a Comment