通过反射,我正在调用一个可能导致异常的方法。我怎样才能将异常传递给我的调用者而没有包装反射围绕它? 我重新抛出InnerException,但这破坏了堆栈跟踪。 示例代码:

public void test1()
{
    // Throw an exception for testing purposes
    throw new ArgumentException("test1");
}

void test2()
{
    try
    {
        MethodInfo mi = typeof(Program).GetMethod("test1");
        mi.Invoke(this, null);
    }
    catch (TargetInvocationException tiex)
    {
        // Throw the new exception
        throw tiex.InnerException;
    }
}

当前回答

基于Paul turner的回答,我提出了一个扩展方法

    public static Exception Capture(this Exception ex)
    {
        ExceptionDispatchInfo.Capture(ex).Throw();
        return ex;
    }

返回ex. ist从未到达,但优点是我可以使用throw ex. capture()作为一行,这样编译器就不会引发一个不是所有代码路径返回值错误。

    public static object InvokeEx(this MethodInfo method, object obj, object[] parameters)
    {
        {
            return method.Invoke(obj, parameters);
        }
        catch (TargetInvocationException ex) when (ex.InnerException != null)
        {
            throw ex.InnerException.Capture();
        }
    }

其他回答

基于Paul turner的回答,我提出了一个扩展方法

    public static Exception Capture(this Exception ex)
    {
        ExceptionDispatchInfo.Capture(ex).Throw();
        return ex;
    }

返回ex. ist从未到达,但优点是我可以使用throw ex. capture()作为一行,这样编译器就不会引发一个不是所有代码路径返回值错误。

    public static object InvokeEx(this MethodInfo method, object obj, object[] parameters)
    {
        {
            return method.Invoke(obj, parameters);
        }
        catch (TargetInvocationException ex) when (ex.InnerException != null)
        {
            throw ex.InnerException.Capture();
        }
    }

可以在不反射地重新抛出之前保留堆栈跟踪:

static void PreserveStackTrace (Exception e)
{
    var ctx = new StreamingContext  (StreamingContextStates.CrossAppDomain) ;
    var mgr = new ObjectManager     (null, ctx) ;
    var si  = new SerializationInfo (e.GetType (), new FormatterConverter ()) ;

    e.GetObjectData    (si, ctx)  ;
    mgr.RegisterObject (e, 1, si) ; // prepare for SetObjectData
    mgr.DoFixups       ()         ; // ObjectManager calls SetObjectData

    // voila, e is unmodified save for _remoteStackTraceString
}

与通过缓存委托调用InternalPreserveStackTrace相比,这浪费了大量的周期,但优点是只依赖于公共功能。下面是一些堆栈跟踪保存函数的常见使用模式:

// usage (A): cross-thread invoke, messaging, custom task schedulers etc.
catch (Exception e)
{
    PreserveStackTrace (e) ;

    // store exception to be re-thrown later,
    // possibly in a different thread
    operationResult.Exception = e ;
}

// usage (B): after calling MethodInfo.Invoke() and the like
catch (TargetInvocationException tiex)
{
    PreserveStackTrace (tiex.InnerException) ;

    // unwrap TargetInvocationException, so that typed catch clauses 
    // in library/3rd-party code can work correctly;
    // new stack trace is appended to existing one
    throw tiex.InnerException ;
}

这是在。net 6中测试的其他一些想法的一个干净、现代的实现:

public static class ExceptionExtensions
{
    [DoesNotReturn]
    public static void Rethrow(this Exception ex) 
        => ExceptionDispatchInfo.Capture(ex).Throw();
}

我想要myObject上的PropertyName属性的值,但这将在使用反射调用方法(根据OP的问题)或其他导致您想要重新抛出内部异常的任何东西时同样有效。

try
{
    object? value = myObject.GetType().GetProperty("PropertyName")?.GetValue(myObject);
}
catch (TargetInvocationException ex)
{
    (ex.InnerException ?? ex).Rethrow();
}

伙计们,你们真酷。我很快就会成为一名死灵法师。

    public void test1()
    {
        // Throw an exception for testing purposes
        throw new ArgumentException("test1");
    }

    void test2()
    {
            MethodInfo mi = typeof(Program).GetMethod("test1");
            ((Action)Delegate.CreateDelegate(typeof(Action), mi))();

    }

另一个使用异常序列化/反序列化的示例代码。 它不要求实际的异常类型是可序列化的。 此外,它只使用公共/受保护的方法。

    static void PreserveStackTrace(Exception e)
    {
        var ctx = new StreamingContext(StreamingContextStates.CrossAppDomain);
        var si = new SerializationInfo(typeof(Exception), new FormatterConverter());
        var ctor = typeof(Exception).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(SerializationInfo), typeof(StreamingContext) }, null);

        e.GetObjectData(si, ctx);
        ctor.Invoke(e, new object[] { si, ctx });
    }