13

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

Community
  • 1
  • 1
Timothy Shields
  • 75,459
  • 18
  • 120
  • 173

4 Answers4

10

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();
Timothy Shields
  • 75,459
  • 18
  • 120
  • 173
1

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.

feralin
  • 3,268
  • 3
  • 21
  • 37
  • So, that's the problem actually: `e.InnerException.StackTrace` is correct *before* rethrowing `e.InnerException`. However, the `throw` operation sets the `StackTrace` property of the `Exception`. So *after* the `throw` the original `StackTrace` is lost. – Timothy Shields Mar 27 '13 at 21:18
  • @TimothyShields it sets the `StackTrace` even if there is already a value there? If so, then I don't know what your answer is... hopefully someone else will explain it, because I'm curious as well now! – feralin Mar 27 '13 at 23:02
  • Yes, the `throw` command seems to have deep voodoo side-effects like modifying readonly properties of the `Exception` it's throwing. – Timothy Shields Mar 28 '13 at 01:08
0

The best option is Option 3: don't use reflection at all, but instead use Expression<T>.Compile().

Instead of doing this:

static void CallMethodWithReflection(MethodInfo method)
{
    try
    {
        method.Invoke(null, new object[0]);
    }
    catch (TargetInvocationException exp)
    {
        throw exp.InnerException;
    }
}

Try to aim for this:

private static void CallMethodWithExpressionCompile(MethodInfo method)
{
    Expression.Lambda<Action>(Expression.Call(method)).Compile()();
}

The caveat is that you need to know the method signature, although you can write code that dynamically builds the expression to fit one of several signatures.

You may not always be able to use this technique, but when you do it is the best option. For all intents and purposes it is like calling any other delegate. It is also faster than reflection if you make multiple calls (in this case compile only once and keep a handle on the compiled delegate).

Amir Abiri
  • 8,847
  • 11
  • 41
  • 57
0

I had a similar issue and came up with this:

/// <summary>
/// Attempts to throw the inner exception of the TargetInvocationException
/// </summary>
/// <param name="ex"></param>
[DebuggerHidden]
private static void ThrowInnerException(TargetInvocationException ex)
{
    if (ex.InnerException == null) { throw new NullReferenceException("TargetInvocationException did not contain an InnerException", ex); }

    Exception exception = null;
    try
    {
        //Assume typed Exception has "new (String message, Exception innerException)" signature
        exception = (Exception) Activator.CreateInstance(ex.InnerException.GetType(), ex.InnerException.Message, ex.InnerException);
    }
    catch
    {
        //Constructor doesn't have the right constructor, eat the error and throw the inner exception below
    }

    if (exception == null ||
        exception.InnerException == null ||
        ex.InnerException.Message != exception.Message)
    {
        // Wasn't able to correctly create the new Exception.  Fall back to just throwing the inner exception
        throw ex.InnerException;
    }
    throw exception;
}

An Example of it's use is below:

try
{
    return typeof(MyType).GetMethod(methodName, BindingFlags.Public | BindingFlags.Static)
                                    .MakeGenericMethod(new[] { myType) })
                                    .Invoke(null, parameters);
}
catch (TargetInvocationException ex)
{
    ThrowInnerException(ex);
    throw new Exception("Throw InnerException didn't throw exception");
}
Daryl
  • 18,592
  • 9
  • 78
  • 145
  • How does this maintain the stack trace of the inner exception? – Timothy Shields May 28 '14 at 15:04
  • @TimothyShields The Inner Exception of the TargetInvocationException is added as the inner exception of the new created, correctly typed exception. This is kind of a happy medium. If you need the full stack trace, look at the Inner Exception. Since the new exception that is being thrown is of the same type as the inner exception, the standard catch(ExceptionType) logic should still work. – Daryl May 28 '14 at 16:59