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

StackOverflow https://stackoverflow.com/questions/57383

  •  09-06-2019
  •  | 
  •  

문제

나는 리플렉션을 통해 예외를 일으킬 수 있는 메서드를 호출하고 있습니다.래퍼 리플렉션을 적용하지 않고 호출자에게 예외를 어떻게 전달할 수 있나요?
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 비동기 언어 기능을 동기 언어 기능과 더 유사하게 만들기 위한 인스턴스입니다.

다른 팁

그것 ~이다 리플렉션 없이 다시 발생하기 전에 스택 추적을 보존할 수 있습니다.

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

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

throw;

그런 다음 나중에 innerException을 추출합니다.

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

예외를 발생시키기 전에 확장 메서드를 호출하면 원래 스택 추적이 유지됩니다.

반성도 더 하고...

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의 일부가 아니기 때문에 언제든지 문제가 발생할 수 있다는 점을 명심하세요.다음에 대한 추가 토론을 참조하십시오. 모노 버그질라.

첫 번째: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 메소드는 라인 번호입니다. throw new Exception( "TEST" ) 선.

그러나 사례 3에서는 소스 코드 줄 번호가 있는 스택 추적을 제공합니다. CallingMethod 메소드는 라인 번호입니다. throw 부르다.이는 다음을 의미합니다. throw new Exception( "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))();

    }

예외 직렬화/역직렬화를 사용하는 또 다른 샘플 코드입니다.실제 예외 유형을 직렬화할 필요는 없습니다.또한 공개/보호된 방법만 사용합니다.

    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 });
    }
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top