Exception Filter


C# does not support exception filter. However, VB and IL support it. To add exception filter to C#, we can build a function in VB or IL, then call it in C#.


The example below is one way we may implement the function to support exception filter. It asks the caller to provide four delegates, and call them at the right places.


Imports Microsoft.VisualBasic


 


Namespace Utility


 


    Public Delegate Sub TryDelegate()


    Public Delegate Function FilterDelegate(ByVal exception As System.Exception) As Boolean


    Public Delegate Sub CatchDelegate(ByVal exception As System.Exception)


    Public Delegate Sub FinallyDelegate()


 


    Public Class ExceptionFilter


        Inherits System.Object


 


        Public Shared Sub TryFilterCatch(ByVal tryDelegate As TryDelegate, ByVal filterDelegate As FilterDelegate, ByVal catchDelegate As CatchDelegate, ByVal finallyDelegate As FinallyDelegate)


            Try


                tryDelegate()


            Catch ex As System.Exception When catchDelegate <> Nothing And filterDelegate(ex)


                catchDelegate(ex)


            Finally


                If (finallyDelegate <> Nothing) Then


                    finallyDelegate()


                End If


            End Try


        End Sub


 


    End Class


 


End Namespace


 


In C# we can call Utility.TryFilterCatch as the following:


using System;


using Utility;

 


namespace ExceptionFilterUsage


{


    class Program


    {


        static void Main(string[] args)


        {


            TryDelegate tryDelegate = delegate()


            {


                Console.WriteLine(“In try.”);


                throw new ApplicationException();


            };


 


            FilterDelegate filterDelegate = delegate(Exception e)


            {


                Console.WriteLine(“In exception filter.”);


 


                if (e is ApplicationException)


                {


                    Console.WriteLine(“ApplicationException is thrown, which should never happen.”);


                    return false;


                }


                return true;


            };


 


            CatchDelegate catchDelegate = delegate(Exception e)


            {


                Console.WriteLine(“In Catch: Exception: {0}”, e);


            };


 


            FinallyDelegate finallyDelegate = delegate()


            {


                Console.WriteLine(“In finally.”);


            };


 


            ExceptionFilter.TryFilterCatch(tryDelegate, filterDelegate, catchDelegate, finallyDelegate);


        }


    }


}


 

Comments (7)

  1. Kris says:

    Now this might be a silly question, but I wonder if there is a way by which you can specify exception handling a la AOP sytle and let the handlers get kicked off by convention rather than code. I know C# has made some rapid advances compared to language like Java, but there is still background noise in the code.

  2. David Levine says:

    Hi Jungfeng,

    Several years ago I wrote something similar that provides exception filtering functionality, except I wrote it using IL. I also added capability for a fault handler, which are like finally handlers but only execute if an exception is thrown.

    I’ve been wanting to write a blog about it, along with related topics, but don’t have the time, so I figured I’d just add the code to this reply and see what you think.

    There are very interesting timing and sequencing relationships here that are worth a blog in itself. Perhaps if I have time…

    I wished C# exposed exception filters. And fault handlers too.

    To compile this, copy this into a file, such as EFilter.il and execute

    ilasm.exe /debug EFilter.il /DLL

    // =========== copy from here =============================

    .assembly extern mscorlib { auto }

    .assembly ExceptionFilterFaultHandler4

    {

    .ver 1:5:0:0

    }

    .module ExceptionFilterFaultHandler4.dll

    .namespace ExceptionFilters

    {

    // declare enum that tells runtime what to do with exception

    .class public sealed enum ExceptionDisposition

    {

    .field public specialname int32 __value;

    .field public static literal valuetype ExceptionFilters.ExceptionDisposition ContinueSearch = int32(0)

    .field public static literal valuetype ExceptionFilters.ExceptionDisposition ExecuteHandler = int32(1)

    }

    // NOTE: Async methods in delegates not supported – all calls must be synchronous.

    // declare delegate for user routine that runs inside the region protected by the exception filter

    // and in the exception handler

    .class public sealed UserRoutine

    extends [mscorlib]System.MulticastDelegate

    {

    .method public hidebysig instance

    void .ctor(object MethodsClass,native unsigned int MethodPtr)

    runtime managed{}

    // this routine defines the signature of the delegate

    .method public hidebysig virtual instance

    void Invoke()

    runtime managed{}

    } // UserRoutine

    // declare delegate that gets invoked in handler;

    // this is the user’s exception catch handler code that gets executed if the

    // filter returns ExceptionDisposition.ExecuteHandler

    .class public sealed ExecuteHandler

    extends [mscorlib]System.MulticastDelegate

    {

    .method public hidebysig instance

    void .ctor(object MethodsClass,native unsigned int MethodPtr)

    runtime managed{}

    // this routine defines the signature of the delegate

    .method public hidebysig virtual instance

    void Invoke(class [mscorlib]System.Exception e)

    runtime managed{}

    } // ExecuteHandler

    // declare delegate that gets invoked in filter;

    // this is the user’s exception filter code that determines how the exception is handled

    .class public sealed ExceptionFilter

    extends [mscorlib]System.MulticastDelegate

    {

    .method public hidebysig instance

    void .ctor(object MethodsClass,native unsigned int MethodPtr)

    runtime managed{}

    // this routine defines the signature of the delegate

    .method public hidebysig virtual instance

    valuetype ExceptionFilters.ExceptionDisposition Invoke(class [mscorlib]System.Exception e)

    runtime managed{}

    } // ExceptionFilter

    // declare delegate that gets invoked in fault handler;

    // this is the user’s exception fault handler code that gets executed if a

    // fault occurs in the try block, regardless of what is returned from the

    // filter

    .class public sealed FaultHandler

    extends [mscorlib]System.MulticastDelegate

    {

    .method public hidebysig instance

    void .ctor(object MethodsClass,native unsigned int MethodPtr)

    runtime managed{}

    // this routine defines the signature of the delegate

    .method public hidebysig virtual instance

    void Invoke()

    runtime managed{}

    } // FaultHandler

    // declare delegate that gets invoked in finally block;

    // this is the user’s code that gets executed regardless of

    // anything else

    .class public sealed FinallyHandler

    extends [mscorlib]System.MulticastDelegate

    {

    .method public hidebysig instance

    void .ctor(object MethodsClass,native unsigned int MethodPtr)

    runtime managed{}

    // this routine defines the signature of the delegate

    .method public hidebysig virtual instance

    void Invoke()

    runtime managed{}

    } // FinallyHandler

    // The ECMA specification states that a block of code that is protected by an exception block has only

    // one kind of handler. If you emit a fault handler in an exception block that also contains a catch

    // handler or a finally handler, the resulting code is unverifiable. No exception is thrown when the

    // code is emitted, but when it is compiled the just-in-time (JIT) compiler throws

    // an InvalidProgramException. Therefore, you must use nested exception blocks for multiple kinds of handlers,

    // This code is organized as:

    //    try

    //      try

    //        try

    //          **user code**

    //        fault

    //      filter

    //      catch

    //    finally

    // The fault handler is nested within the filter/catch handler, which in turn is nested within

    // the finally handler.

    // This checks for nulls before invoking the callbacks, except for the user code.

    .class public abstract auto ansi sealed beforefieldinit ExceptionFilterAndFaultWrapper

          extends [mscorlib]System.Object

    {

     //.custom instance void [mscorlib]System.Diagnostics.DebuggerNonUserCodeAttribute::.ctor() = ( 01 00 00 00 )

     .method public hidebysig static class [mscorlib]System.Exception

             Invoke(class ExceptionFilters.UserRoutine routine,

                    class ExceptionFilters.ExceptionFilter filterBlock,

                    class ExceptionFilters.ExecuteHandler catchBlock,

                    class ExceptionFilters.FaultHandler faultBlock,

                    class ExceptionFilters.FinallyHandler finallyBlock

                     ) cil managed

     {

       //.maxstack 2

       .locals init (

    [0] class [mscorlib]System.Exception e,

    [1] bool isNull )

       .try // outermost try for finally handler

       {

    .try // try for filter/catch

    {

    .try // inner try for fault

    {

    // this ASSUMES that the UserRoutine is NEVER null

    ldarg.0 // push ‘routine’ argument onto execution stack

    callvirt   instance void ExceptionFilters.UserRoutine::Invoke()

    leave.s EOF

    } // .try // inner try for fault

    fault

    {

    // null check

    ldarg.3 // push ‘fault handler’ argument onto execution stack

    ldnull // load null onto eval stack

    ceq // Compares two values. Push 1 if equal, otherwise push 0

    ldc.i4.0 // push 0 onto stack; if previous result == 0 then is not null

    ceq // Compares two values. Push 1 if equal, otherwise push 0

    brfalse.s dontCallFault // dont call fault

    // call fault handler

    ldarg.3  // push ‘fault handler’ argument onto execution stack

    callvirt   instance void ExceptionFilters.FaultHandler::Invoke()

    dontCallFault:

    endfault

    }

    }  // end .try

    filter

    {

    // verify the object is an exception

    castclass class [mscorlib]System.Exception

    // 1st save exception object on stack

    stloc.0   // store arg on stack into local variable 0

    // null check

    ldarg.1 // push ‘filter handler’ argument onto execution stack

    ldnull // load null onto eval stack

    ceq // Compares two values. Push 1 if equal, otherwise push 0

    brtrue.s  dontCallFilter // dont call filter (handler was null

    // call filter

    ldarg.1 // push ‘filter’ argument onto execution stack

    ldloc.0 // load exception from local variable 0 onto stack

    callvirt   instance valuetype ExceptionFilters.ExceptionDisposition ExceptionFilters.ExceptionFilter::Invoke(class [mscorlib]System.Exception)

    // check result

    ldc.i4.1 // is result == ExecuteHandler?

    ceq // compare and push result on stack. If equal it pushes 1, otherwise pushes 0

    // if return was == 1 then execute handler (push 1 on stack) otherwise continue search (push 0)

    br exitFilter

    dontCallFilter:

    ldc.i4.1 // push 1 onto stack (execute handler)

    exitFilter:

    endfilter

    }

    { // this is the actual handler code. this calls the user’s handler code.

    // there’s no way to specify the exception type here.

    // verify the object is an exception

    castclass class [mscorlib]System.Exception

    stloc.0   // store arg on stack into local variable 0

    // null check

    ldarg.2 // push ‘catch handler’ argument onto execution stack

    ldnull // load null onto eval stack

    ceq // Compares two values. Push 1 if equal, otherwise push 0

    // If the value is null then the branch is true

    brtrue.s  DontCallCatch // don’t call catch handler (it was null)

    // call the catch handler

    ldarg.2  // push ‘catch handler’ argument onto execution stack

    ldloc.0 // load exception onto stack

    callvirt   instance void ExceptionFilters.ExecuteHandler::Invoke(class [mscorlib]System.Exception)

    leave.s EOF

    DontCallCatch:

    //ldloc.0 // load exception onto stack (for return)

    // if there’s no handler, just rethrow the execption

    rethrow // don’t swallow it, rethrow

    }  // end catch handler

    } // .try // outermost try for finally handler

    finally

    {

    // null check

    ldarg.s 4 // push ‘finally handler’ argument onto execution stack

    ldnull // load null onto eval stack

    ceq // Compares two values. Push 1 if equal, otherwise push 0

    brtrue.s  dontCallFinally // call finally handler

    ldarg.s 4  // push ‘finally handler’ argument onto execution stack

    callvirt   instance void ExceptionFilters.FinallyHandler::Invoke()

    dontCallFinally:

    endfinally

    }

       EOF: ldloc.0 // return the first local

       ret

     } // end of method ExceptionBoundary::Invoke

    } // end of class ExceptionBoundary

    } // end of .namespace ExceptionFilters

    // ====== end copy here ==================================

  3. junfeng says:

    David,

    The fault handler feels not as useful with filter/finally handler. You can run the code in either the filter handler or the finally handler.

  4. David Levine says:

    Thanks for trying out the IL code.

    I tend to agree that it’s not as useful when combined with the try-filter-catch-finally

    I think a better usage for a fault handler is in isolation from the standard try-catch-finally. I’d prefer to see a language syntax so that you can use just a fault handler, as in try-fault, and then you can place it where it’s needed.

    I think there is a need for a fault handler because I’ve seen the following pattern used extensively.

    try

    {

     CodeThatCanThrow();

    }

    catch( Exception )

    {

     if ( someCondition )

       Cleanup();

     throw;

    }

    A variation is when it’s an empty catch (no exception type specified) so that it catches non-CLSCompliant exceptions.

    The pattern that emerges from this is that the catch clause does not even look at the exception and does not care what type of error that occurred, it must perform some cleanup regardless. It’s not that this does not work, it’s that because the language does not provide a try-fault, the author had to use some other mechanism.

    I have several issues with doing this: first, the intent is not clear. You have to parse the statements in the catch clause to understand that it’s not really doing anything with the exception, not even logging it. Second, catches have side effects, such as running more deeply nested finally blocks and unwinding the stack, before the catch handler executes. Third, I have seen this implemented incorrectly, where a "throw ex" is used, that discards the original stack trace – it’s too easy to get it wrong. Fourth, for myself, as a general rule of thumb, if it’s worth the effort of catching it and it needs to be rethrown, then it ought to be wrapped inside another exception that adds meaningful content and context.

    I’d rather see:

    try

    {

     CodeThatCanThrow();

    }

    fault

    {

     if ( someCondition )

       Cleanup();

    }

    To me the intent of this is much clearer. You can still nest this within a try-catch if other handling is needed.

    An example of this pattern is in WindowsBase.dll. If you use Reflector to look at method MS.Internal.IO.Zip.ZipArchive.OpenOnFile(String,FileMode,FileAccess,FileShare,Boolean), you will see this this code

    try

    {

     stream = new FileStream(path, mode, access, share, 0x1000, streaming);

     ValidateModeAccessShareStreaming(stream, mode, access, share, streaming);

     archive = new ZipArchive(stream, mode, access, streaming, true);

    }

    catch

    {

     if (stream != null)

     {

       stream.Close();

     }

     throw;

    }

    The usage here is that if the operation got far enough along to allocate a stream object, and the operation failed after that point, then close the stream and abort. It does not matter why it failed, and no corrective action could be taken. But if no errors occurred, then do nothing and leave the stream open.

    I’ve seen this pattern used extensively. I wrote an FxCop rule that discovers similar patterns and that’s how I found this one.

    Regards,

    Dave

  5. junfeng says:

    catch and re-throw is a bad pattern, as once the exception is caught, the original exception context is gone, and the exception is no longer debuggable.

    For the example, I will either do the clean up in finally, or in the filter handler.

    use the finally as an example:

    bool success = false;

    try

    {

       DoSomeWork();

       success = true;

    }

    finally

    {

       if (!success)

       {

           Cleanup();

       }

    }

  6. Somebody posted a comment up my blog which contained something along the lines of&#160; “and VB .NET

  7. Somebody posted a comment up my blog which contained something along the lines of&#160; “and VB .NET