保留动态调用方法的异常

问题描述 投票:0回答:5

我想动态调用一个

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 块中”的要求分离的方法。

c# exception reflection
5个回答
10
投票

这是我想出的解决方案。它完成了工作。我仍然对其他答案感兴趣,因为可能有更简单或更干净的东西。

  • 当您想要
    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();

1
投票

不,我不相信有一种方法可以兼得两者的好处。但是,抛出

e.InnerException
仍然允许您获取原始堆栈跟踪,因为您可以简单地使用
e.InnerException.StackTrace
来获取原始堆栈跟踪。所以,简而言之,您应该使用选项 1


0
投票

最好的选择是选项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()();
}

需要注意的是,您需要知道方法签名,尽管您可以编写动态构建表达式以适合多个签名之一的代码。

您可能并不总是能够使用此技术,但当您使用时,它就是最好的选择。出于所有意图和目的,这就像调用任何其他代表一样。如果进行多次调用,它也比反射更快(在这种情况下仅编译一次并保留已编译委托的句柄)。


0
投票

我有类似的问题并想出了这个:

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

0
投票

从.NET 4.5开始可以按如下方式处理,

void DoDynamicCall()
{
    MethodInfo method = /*referencing MyClass method void MyStaticFunction(int x)*/;
    try
    {
        method.Invoke(null, new object[] { 5 });
    }
    catch (TargetInvocationException e)
    {
        //Capture an exception and re-throw it without changing the stack-trace
        ExceptionDispatchInfo.Capture(e.InnerException ?? e).Throw();
        throw;
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.