Programing

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

lottogame 2020. 3. 21. 10:10
반응형

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.

awaitC # 언어 기능 으로 인해 도입 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 );
    }
}

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


더 많은 반사 ...

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에 대한 추가 토론을 참조하십시오 .


아무도 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와 유사하지만 원래 예외의 유형을 변경하기 때문에 실제로는 다시 발생하지 않습니다.


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


얘들 아, 당신은 시원하다. 나는 곧 네크로맨서가 될 것이다.

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

    }

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

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

Paul Turners의 답변에 따라 확장 방법을 만들었습니다.

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

return exIST는 도달하지 못했다하지만 장점은 내가 사용할 수 있다는 것입니다 throw ex.Capture()컴파일러가 제기되지 않도록 한 라이너로 not all code paths return a value오류가 발생했습니다.

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

참고 URL : https://stackoverflow.com/questions/57383/how-to-rethrow-innerexception-without-losing-stack-trace-in-c

반응형