[C#] 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
.
답변
이다 반사없이 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 );
}
}
예외를 던지기 전에 확장 메소드를 호출하면 원래 스택 추적이 유지됩니다.
답변
아무도 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와 유사하지만 원래 예외의 유형을 변경하기 때문에 실제로 다시 던지는 것은 아닙니다.
답변
더 많은 반사 …
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에 대한 추가 토론을 참조하십시오 .
답변
첫째, TargetInvocationException을 잃지 마십시오. 디버깅 할 때 유용한 정보입니다.
둘째 : TIE를 예외 유형으로 InnerException으로 감싸고 필요한 것에 연결하는 OriginalException 속성을 배치하십시오 (그리고 전체 호출 스택을 그대로 유지하십시오).
셋째 : 방법에서 TIE 버블을 제거하십시오.