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


305

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

1
부두를 요구하지 않는 다른 방법이 있습니다. 여기에 대한 답변을보십시오 : stackoverflow.com/questions/15668334/…
Timothy Shields

동적으로 호출 된 메소드에서 발생하는 예외는 "호출 대상에서 예외가 발생했습니다"예외의 내부 예외입니다. 자체 스택 추적이 있습니다. 실제로 걱정할 것이 많지 않습니다.
ajeh

답변:


481

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

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

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

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

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


11
Exception.Rethrow () 확장 메소드에 적합한 후보입니까?
nmarler

8
ExceptionDispatchInfo 클래스는 System.Runtime.ExceptionServices 네임 스페이스에 있으며 .NET 4.5 이전에는 사용할 수 없습니다.
yoyo

53
throw;컴파일러는 .Throw ()가 항상 예외를 던진다는 것을 알지 못하므로 .Throw () 행 뒤에 규칙을 넣어야 할 수도 있습니다 . throw;결과로 호출되지는 않지만 메소드에 반환 객체가 필요하거나 비동기 함수인지 여부에 따라 컴파일러는 불평하지 않습니다.
Todd

5
@Taudris이 질문은 특별히 내부 예외를 다시 던지는 것에 관한 것이며,이 예외는 특별히 처리 할 수 ​​없습니다 throw;. throw ex.InnerException;스택 트레이스 를 사용 하면 다시 발생하는 지점에서 다시 초기화됩니다.
Paul Turner

5
@amitjhaExceptionDispatchInfo.Capture(ex.InnerException ?? ex).Throw();
Vedran

86

이다 반사없이 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 ;
}

멋지다.이 기능을 실행 한 후에는 어떻게해야합니까?
vdboor

2
실제로, 그것은 호출하는 것보다 훨씬 느리지 않습니다 InternalPreserveStackTrace(10000 회 반복으로 약 6 % 느림). 리플렉션을 통해 필드에 직접 액세스하는 것은 호출하는 것보다 약 2.5 % 빠릅니다.InternalPreserveStackTrace
Thomas Levesque

1
e.Data문자열이나 고유 객체 키와 함께 사전을 사용하는 것이 좋습니다 ( static readonly object myExceptionDataKey = new object ()그러나 예외를 직렬화 해야하는 경우이 작업을 수행하지 마십시오). e.Message구문 분석 할 수있는 코드가있을 수 있으므로 수정하지 마십시오 e.Message. 구문 분석 e.Message은 악의적이지만 예외적 인 관행이 좋지 않은 타사 라이브러리를 사용해야하는 경우 다른 선택은 없을 수 있습니다.
Anton Tykhyy

10
DoFixups에 직렬화 ctor가없는 사용자 지정 예외가 발생했습니다
ruslander

3
예외에 직렬화 생성자가없는 경우 제안 된 솔루션이 작동하지 않습니다. 어떤 경우에도 잘 작동하는 stackoverflow.com/a/4557183/209727 에서 제안 된 솔루션을 사용하는 것이 좋습니다 . .NET 4.5의 경우 ExceptionDispatchInfo 클래스 사용을 고려하십시오.
Davide Icardi

33

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

throw;

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


21
또는 try / catch를 모두 제거하십시오.
Daniel Earwicker

6
@Earwicker. try / catch를 제거하는 것은 일반적으로 예외를 전파하기 전에 정리 코드가 필요한 경우를 무시하기 때문에 좋은 해결책이 아닙니다.
Jordan

12
@Jordan-정리 코드는 catch 블록이 아닌 finally 블록에 있어야 함
Paolo

17
@Paolo-모든 경우에 실행되어야한다면, 그렇습니다. 실패한 경우에만 실행해야한다면, 아닙니다.
chiccodoro

4
InternalPreserveStackTrace는 스레드 안전하지 않으므로 이러한 예외 상태에 대해 2 개의 스레드가있는 경우 신이 우리 모두에게 자비를 베풀 수 있습니다.
Rob

13
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 4.0에서 InternalPreserveStackTrace는 이제 Reflector에서 전혀 볼 수 없으며 메소드가 완전히 비어 있음을 알 수 있습니다!
사무엘 잭

스크래치 : RC를보고있었습니다. 베타에서는 구현을 다시했습니다!
사무엘 잭

3
제안 : exserveStackTrace를 변경하여 ex를 반환 한 다음 예외를 던지려면 다음과 같이 말할 수 있습니다. throw ex.PreserveStackTrace ();
Simon_Weaver 5

왜 사용 Action<Exception>합니까? 여기에 사용하는 정적 메소드
Kiquenet

13

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


4
나는 항상 'throw'가 스택 트레이스를 재설정하지 않았다고 생각했습니다 ( 'throw e'와 반대).
Jesper Matthiesen

@JesperMatthiesen 실수했을 수도 있지만 예외가 동일한 파일에서 발생하여 잡혔는 지 여부에 달려 있다고 들었습니다. 동일한 파일 인 경우 스택 추적이 손실되고 다른 파일 인 경우 보존됩니다.
jahu

10

더 많은 반사 ...

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


28
프레임 워크 클래스에 대한 문서화되지 않은 내부 세부 정보에 의존하기 때문에 이것은 정말 나쁜 생각입니다.
Daniel Earwicker

1
리플렉션없이 스택 추적을 보존 할 수 있습니다. 아래를 참조하십시오.
Anton Tykhyy

1
내부 InternalPreserveStackTrace메소드를 호출하는 것이 똑같은 일을하고 앞으로 변경 될 가능성이 적기 때문에 더 나을 것입니다 ...
Thomas Levesque

1
InternalPreserveStackTrace가 Mono에 존재하지 않기 때문에 실제로는 더 나쁩니다.
skolima

5
@ 다니엘-글쎄, 정말, 정말, 나쁜 생각을 던져; 모든 .net 개발자가 그렇지 않다고 믿도록 훈련되었을 때 스택 추적을 재설정합니다. NullReferenceException의 원인을 찾을 수없고 찾을 수 없기 때문에 고객 / 주문을 잃는 경우에도 정말, 정말, 나쁜 일입니다. 나를 위해 '언급되지 않은 세부 사항'과 분명히 모노를 능가합니다.
Simon_Weaver 5

10

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


5

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

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

    }

1
좋은 생각이지만 항상 호출하는 코드를 제어하지는 않습니다 .Invoke().
Anton Tykhyy

1
그리고 컴파일 타임에 인수 / 결과의 유형을 항상 알지는 못합니다.
Roman Starkov

3

예외 직렬화 / 직렬화를 사용하는 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 });
    }

실제 예외 유형이 직렬화 가능하지 않아도됩니까?
Kiquenet

3

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();
        }
    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.