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
비동기 언어 기능을 동기 언어 기능과 더 유사하게 만들기 위한 인스턴스입니다.
다른 팁
그것 ~이다 리플렉션 없이 다시 발생하기 전에 스택 추적을 보존할 수 있습니다.
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 });
}