通过反射,我正在调用一个可能导致异常的方法。我怎样才能将异常传递给我的调用者而没有包装反射围绕它? 我重新抛出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;
    }
}

当前回答

没有人解释过ExceptionDispatchInfo之间的区别。Capture(ex). throw()和一个普通的throw,所以它在这里。

重新抛出捕获的异常的完整方法是使用ExceptionDispatchInfo。Capture(ex). throw()(仅在. net 4.5中可用)。

下面是测试这一点的必要情况:

1.

void CallingMethod()
{
    //try
    {
        throw new Exception( "TEST" );
    }
    //catch
    {
    //    throw;
    }
}

2.

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch( Exception ex )
    {
        ExceptionDispatchInfo.Capture( ex ).Throw();
        throw; // So the compiler doesn't complain about methods which don't either return or throw.
    }
}

3.

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch
    {
        throw;
    }
}

4.

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch( Exception ex )
    {
        throw new Exception( "RETHROW", ex );
    }
}

案例1和案例2将为您提供一个堆栈跟踪,其中CallingMethod方法的源代码行号是抛出新异常(“TEST”)行的行号。

然而,情形3将为您提供一个堆栈跟踪,其中CallingMethod方法的源代码行号是抛出调用的行号。这意味着如果抛出新的异常(“TEST”)行被其他操作包围,则您不知道异常实际上是在第几行抛出的。

情况4与情况2类似,因为原始异常的行号被保留,但不是真正的重抛出,因为它改变了原始异常的类型。

其他回答

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

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

    }
public static class ExceptionHelper
{
    private static Action<Exception> _preserveInternalException;

    static ExceptionHelper()
    {
        MethodInfo preserveStackTrace = typeof( Exception ).GetMethod( "InternalPreserveStackTrace", BindingFlags.Instance | BindingFlags.NonPublic );
        _preserveInternalException = (Action<Exception>)Delegate.CreateDelegate( typeof( Action<Exception> ), preserveStackTrace );            
    }

    public static void PreserveStackTrace( this Exception ex )
    {
        _preserveInternalException( ex );
    }
}

在抛出异常之前调用该扩展方法,它将保留原始堆栈跟踪。

这是在。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();
}

首先:不要丢失TargetInvocationException——当你想调试东西时,它是很有价值的信息。 第二:将TIE包装为您自己的异常类型中的InnerException,并放置一个OriginalException属性,该属性链接到您需要的内容(并保持整个调用堆栈完整)。 第三:让TIE冒泡退出方法。

没有人解释过ExceptionDispatchInfo之间的区别。Capture(ex). throw()和一个普通的throw,所以它在这里。

重新抛出捕获的异常的完整方法是使用ExceptionDispatchInfo。Capture(ex). throw()(仅在. net 4.5中可用)。

下面是测试这一点的必要情况:

1.

void CallingMethod()
{
    //try
    {
        throw new Exception( "TEST" );
    }
    //catch
    {
    //    throw;
    }
}

2.

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch( Exception ex )
    {
        ExceptionDispatchInfo.Capture( ex ).Throw();
        throw; // So the compiler doesn't complain about methods which don't either return or throw.
    }
}

3.

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch
    {
        throw;
    }
}

4.

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch( Exception ex )
    {
        throw new Exception( "RETHROW", ex );
    }
}

案例1和案例2将为您提供一个堆栈跟踪,其中CallingMethod方法的源代码行号是抛出新异常(“TEST”)行的行号。

然而,情形3将为您提供一个堆栈跟踪,其中CallingMethod方法的源代码行号是抛出调用的行号。这意味着如果抛出新的异常(“TEST”)行被其他操作包围,则您不知道异常实际上是在第几行抛出的。

情况4与情况2类似,因为原始异常的行号被保留,但不是真正的重抛出,因为它改变了原始异常的类型。