programing

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

linuxpc 2023. 5. 6. 14:07
반응형

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에는 이제 클래스가 있습니다.

이렇게 하면 스택 추적을 변경하지 않고 예외를 캡처하고 다시 던질 수 있습니다.

using ExceptionDispatchInfo = 
    System.Runtime.ExceptionServices.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 ;
}

제 생각에 당신의 최선의 방법은 이것을 캐치 블록에 넣는 것입니다.

throw;

그런 다음 나중에 내부 예외를 추출합니다.

아무도 사이의 차이점을 설명하지 않았습니다.ExceptionDispatchInfo.Capture( ex ).Throw() 원평.throw자, 여기 있습니다.

은 탐된예외되돌완는방같전다다습니음과법은한지리를 사용하는 입니다.ExceptionDispatchInfo.Capture( ex ).Throw()으할 수 있습니다 4 순 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 );
    }
}

는 "case 1"과 "case 2"의 줄가 있는 합니다.CallingMethod의 행 입니다.throw new Exception( "TEST" )선을

줄인 나은스, 케이신 3당게스의 스택 입니다. 여기서 소스 코드 라인 번호는CallingMethod의 행 입니다.throw에 콜. 은만에약이것▁the만에약▁call▁if▁that것이.throw new Exception( "TEST" )라인은 다른 작업에 의해 둘러싸여 있으며, 예외가 실제로 몇 번째 라인 번호로 던져졌는지 알 수 없습니다.

사례 4는 원래 예외의 줄 번호가 보존되기 때문에 사례 2와 유사하지만, 원래 예외의 유형을 변경하기 때문에 실제 되돌리기는 아닙니다.

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

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

Paul Turners 답변을 기반으로 확장 방법을 만들었습니다.

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

return ex나는 도달하지 못했지만 장점은 사용할 수 있다는 것입니다.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();
        }
    }

훨씬 더 많은 반성...

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의 일부가 아니므로 언제든지 중단될 수 있습니다.모노 버그질라에 대한 추가 논의를 참조하십시오.

첫 번째: 대상을 잃지 마십시오.호출예외 - 디버깅할 때 유용한 정보입니다.
두 번째: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))();

    }

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

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

이것은 여기서 테스트된 다른 아이디어들 중 일부를 깨끗하고 현대적으로 구현한 것입니다.NET 6:

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

나는 그 가치를 원했습니다.PropertyName에 대한 재산.myObject그러나 이것은 반사를 사용하여 메서드를 호출하거나(OP의 문제에 따라) 내부 예외를 다시 적용하려는 결과를 초래하는 다른 모든 경우에도 마찬가지로 작동합니다.

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

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

반응형