从保动态调用的方法例外(Preserving exceptions from dynamically

2019-07-28 17:04发布

  • 有关
  • 有关

我想动态调用MethodInfo对象,并有获得从里面向外传递,如果它被称为正常抛出的异常。

我好像两个选项。 他们概述如下。

选项1通过维持抛出的异常的类型MyStaticFunction ,但StackTrace是因为毁了throw

选项2保持StackTrace的异常,但异常的类型总是TargetInvocationException 。 我可以拉出来InnerException和它的类型,但是这意味着我不能写,例如:

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

选项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;
    }
}

选项2:

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

我真正想要的是用于呼叫者DoDynamicCall收到异常,仿佛他们已经叫这个:

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

有没有办法让这两个方案1方案2的好处是什么?

编辑:

选项我想我已经 (发明特殊的新C#关键字rethrow当场):

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;
    }
}

这也将消除对这个怪胎的需要,因为你可以使用rethrow e; 代替:

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

在一般情况下,这将是一个分离的方式throw; 从要求“我直接在catch块是。”

Answer 1:

这是我想出了一个解决方案。 它能够完成任务。 我仍然有兴趣在其他的答案,因为可能有一些轻松或清洁剂。

  • 当您想要的功能throw; 但你想传递的例外是不是当前的异常catch块,利用throw Functional.Rethrow(e);
  • 更换try...catch...Functional.TryCatch
  • 更换try...catch...finally...Functional.TryCatchFinally

下面的代码:

//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(); }
    }
}

更新

在.NET 4.5有新的System.Runtime.ExceptionServices.ExceptionDispatchInfo类 。 这可以用来捕获异常:

var capturedException = ExceptionDispatchInfo.Capture(e);

再后来,这是用于恢复抛出异常:

capturedException.Throw();


Answer 2:

不,我不相信有一种方法可以兼得的优点。 然而,抛e.InnerException将仍然允许你来获得原始堆栈跟踪,因为你可以简单地使用e.InnerException.StackTrace来获得原始堆栈跟踪。 因此,简而言之,你应该使用选项1。



Answer 3:

最好的选择是选择3:在所有不使用反射,而是使用Expression<T>.Compile()

而不是这样做:

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

试着瞄准这一点:

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

需要提醒的是,你需要知道方法签名,但您可以编写代码用于动态创建,以适应多个签名的一个表达。

你可能不总是能够利用这种技术,但是,当你这样做是最好的选择。 对于所有意图和目的是一样调用任何其他代表。 它也比反射更快,如果你多次调用(在这种情况下,只编译一次,并保持手柄上编译代表)。



Answer 4:

我有一个类似的问题以及与此想出了:

/// <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;
}

它的使用的一个示例如下:

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");
}


文章来源: Preserving exceptions from dynamically invoked methods