我想动态调用一个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块中”解耦的方法。
答案 0 :(得分:7)
这是我提出的解决方案。它完成了工作。我仍然对其他答案感兴趣,因为可能会有更简单或更清洁的东西。
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
class。这可用于捕获异常:
var capturedException = ExceptionDispatchInfo.Capture(e);
然后用于恢复抛出异常:
capturedException.Throw();
答案 1 :(得分:1)
不,我不相信有办法让两者都有好处。但是,抛出e.InnerException
仍然允许您获取原始堆栈跟踪,因为您只需使用e.InnerException.StackTrace
来获取原始堆栈跟踪。因此,简而言之,您应该使用选项1 。
答案 2 :(得分: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()();
}
需要注意的是,您需要知道方法签名,尽管您可以编写动态构建表达式的代码以适应多个签名之一。
您可能无法始终使用此技术,但是当您这样做时,它是最佳选择。对于所有意图和目的,它就像呼叫任何其他代表。如果你进行多次调用,它也比反射更快(在这种情况下只编译一次,并在编译的委托上保留句柄)。
答案 3 :(得分: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");
}