Search code examples
c#exceptionreflection

Preserving exceptions from dynamically invoked methods


I want to dynamically invoke a MethodInfo object and have any exceptions that get thrown from inside of it pass outward as if it were called normally.

I have two options it seems. They're outlined below.

Option 1 maintains the type of the exception thrown by MyStaticFunction, but the StackTrace is ruined because of the throw.

Option 2 maintains the StackTrace of the exception, but the type of the exception is always TargetInvocationException. I can pull out the InnerException and its type, but that means that I can't write this for example:

try { DoDynamicCall(); }
catch (MySpecialException e) { /* special handling */ }

Option 1:

void DoDynamicCall()
{
    MethodInfo method = /*referencing MyClass method void MyStaticFunction(int x)*/;
    try
    {
        method.Invoke(null, new object[] { 5 });
    }
    catch (TargetInvocationException e)
    {
        throw e.InnerException;
    }
}

Option 2:

void DoDynamicCall()
{
    MethodInfo method = /*referencing MyClass method void MyStaticFunction(int x)*/;
    method.Invoke(null, new object[] { 5 });
}

What I really want is for callers to DoDynamicCall to receive exceptions as if they had called this:

void DoDynamicCall()
{
    MyClass.MyStaticFunction(5);
}

Is there a way to get the benefits of both Option 1 and Option 2?

Edit:

The option I wish I had (invented special new C# keyword rethrow on the spot):

void DoDynamicCall()
{
    MethodInfo method = /*referencing MyClass method void MyStaticFunction(int x)*/;
    try
    {
        method.Invoke(null, new object[] { 5 });
    }
    catch (TargetInvocationException e)
    {
        //Magic "rethrow" keyword passes this exception
        //onward unchanged, rather than "throw" which
        //modifies the StackTrace, among other things
        rethrow e.InnerException;
    }
}

This would also eliminate the need for this weirdo, because you could use rethrow e; instead:

try { ... }
catch (Exception e)
{
    if (...)
        throw;
}

In general, it would be a way to decouple throw; from the requirement "I have to be directly in a catch block."


Solution

  • Here's the solution I came up with. It gets the job done. I'm still interested in other answers as there might be something easier or cleaner.

    • When you want the functionality of throw; but the exception you want to pass on is not the exception of the current catch block, use throw Functional.Rethrow(e);
    • Replace try...catch... with Functional.TryCatch
    • Replace try...catch...finally... with Functional.TryCatchFinally

    Here's the code:

    //Need a dummy type that is throwable and can hold an Exception
    public sealed class RethrowException : Exception
    {
        public RethrowException(Exception inner) : base(null, inner) { }
    }
    
    public static Functional
    {    
        public static Exception Rethrow(Exception e)
        {
            return new RethrowException(e);
        }
    
        public static void TryCatch(Action _try, Action<Exception> _catch)
        {
            try { _try(); }
            catch (RethrowException e) { _catch(e.InnerException); }
            catch (Exception e) { _catch(e); }
        }
    
        public static T TryCatch<T>(Func<T> _try, Func<Exception, T> _catch)
        {
            try { return _try(); }
            catch (RethrowException e) { return _catch(e.InnerException); }
            catch (Exception e) { return _catch(e); }
        }
    
        public static void TryCatchFinally(
            Action _try, Action<Exception> _catch, Action _finally)
        {
            try { _try(); }
            catch (RethrowException e) { _catch(e.InnerException); }
            catch (Exception e) { _catch(e); }
            finally { _finally(); }
        }
    
        public static T TryCatchFinally<T>(
            Func<T> _try, Func<Exception, T> _catch, Action _finally)
        {
            try { return _try(); }
            catch (RethrowException e) { return _catch(e.InnerException); }
            catch (Exception e) { return _catch(e); }
            finally { _finally(); }
        }
    }
    

    Update

    In .NET 4.5 there is the new System.Runtime.ExceptionServices.ExceptionDispatchInfo class. This can be used to capture an exception:

    var capturedException = ExceptionDispatchInfo.Capture(e);
    

    And then later this is used to resume throwing the exception:

    capturedException.Throw();