[C#] C #에서 스택 추적을 잃지 않고 InnerException을 다시 발생시키는 방법은 무엇입니까?

나는 리플렉션을 통해 예외를 일으킬 수있는 방법을 호출하고 있습니다. 래퍼 리플렉션을 사용하지 않고 호출자에게 예외를 전달하려면 어떻게해야합니까?
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;
    }
}



답변

에서 .NET 4.5 지금이 ExceptionDispatchInfo클래스입니다.

이를 통해 스택 추적을 변경하지 않고 예외를 캡처하고 다시 발생시킬 수 있습니다.

try
{
    task.Wait();
}
catch(AggregateException ex)
{
    ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
}

이것은뿐만 아니라 모든 예외에서 작동합니다 AggregateException.

비동기 언어 기능을 동기 언어 기능과 비슷하게 만들기 위해 인스턴스에서 await내부 예외를 풀어 주는 C # 언어 기능 으로 인해 도입되었습니다 AggregateException.


답변

이다 반사없이 rethrowing 전에 스택 추적을 보존 할 수 :

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


답변

가장 좋은 방법은 이것을 캐치 블록에 넣는 것입니다.

throw;

그런 다음 나중에 내부 예외를 추출하십시오.


답변

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

예외를 던지기 전에 확장 메소드를 호출하면 원래 스택 추적이 유지됩니다.


답변

아무도 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.
    }
}

삼.

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메소드 의 소스 코드 라인 번호가 라인의 라인 번호 인 스택 추적을 제공합니다 throw new Exception( "TEST" ).

그러나 케이스 3은 CallingMethod메소드 의 소스 코드 라인 번호 가 throw호출 의 라인 번호 인 스택 추적을 제공합니다 . 이것은 throw new Exception( "TEST" )라인이 다른 오퍼레이션으로 둘러싸여 있다면 실제로 어떤 라인 번호에서 예외가 발생했는지 알 수 없다는 것을 의미합니다 .

사례 4는 원래 예외의 줄 번호가 유지되기 때문에 사례 2와 유사하지만 원래 예외의 유형을 변경하기 때문에 실제로 다시 던지는 것은 아닙니다.


답변

더 많은 반사 …

catch (TargetInvocationException tiex)
{
    // Get the _remoteStackTraceString of the Exception class
    FieldInfo remoteStackTraceString = typeof(Exception)
        .GetField("_remoteStackTraceString",
            BindingFlags.Instance | BindingFlags.NonPublic); // MS.Net

    if (remoteStackTraceString == null)
        remoteStackTraceString = typeof(Exception)
        .GetField("remote_stack_trace",
            BindingFlags.Instance | BindingFlags.NonPublic); // Mono

    // Set the InnerException._remoteStackTraceString
    // to the current InnerException.StackTrace
    remoteStackTraceString.SetValue(tiex.InnerException,
        tiex.InnerException.StackTrace + Environment.NewLine);

    // Throw the new exception
    throw tiex.InnerException;
}

개인 필드는 API의 일부가 아니므로 언제든지 중단 될 수 있습니다. Mono bugzilla에 대한 추가 토론을 참조하십시오 .


답변

첫째, TargetInvocationException을 잃지 마십시오. 디버깅 할 때 유용한 정보입니다.
둘째 : TIE를 예외 유형으로 InnerException으로 감싸고 필요한 것에 연결하는 OriginalException 속성을 배치하십시오 (그리고 전체 호출 스택을 그대로 유지하십시오).
셋째 : 방법에서 TIE 버블을 제거하십시오.