Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Preserving exceptions from dynamically invoked methods

  • Related
  • Related

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."

like image 688
Timothy Shields Avatar asked Mar 27 '13 20:03

Timothy Shields


2 Answers

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();
like image 129
Timothy Shields Avatar answered Oct 18 '22 18:10

Timothy Shields


No, I don't believe there is a way to have the benefits of both. However, throwing e.InnerException will still allow you to get the original stacktrace, because you can simply use e.InnerException.StackTrace to get the original stack trace. So, in short, you should use option 1.

like image 20
feralin Avatar answered Oct 18 '22 19:10

feralin