.NET 예외 포착 및 다시 발생에 대한 모범 사례
-
09-06-2019 - |
문제
예외를 포착하고 다시 발생시킬 때 고려해야 할 모범 사례는 무엇입니까?나는 Exception
사물 InnerException
및 스택 추적이 유지됩니다.이를 처리하는 방식에 있어 다음 코드 블록 간에 차이가 있습니까?
try
{
//some code
}
catch (Exception ex)
{
throw ex;
}
대:
try
{
//some code
}
catch
{
throw;
}
해결책
스택 추적을 보존하는 방법은 다음을 사용하는 것입니다. throw;
이것도 유효해요
try {
// something that bombs here
} catch (Exception ex)
{
throw;
}
throw ex;
기본적으로 해당 지점에서 예외를 발생시키는 것과 같으므로 스택 추적은 throw ex;
성명.
마이크 또한 예외를 통해 예외를 전달할 수 있다고 가정하면(권장됨) 정확합니다.
칼 세귄 가지고있다 예외 처리에 대한 훌륭한 글 그의 프로그래밍의 기초 전자책 게다가 정말 잘 읽었습니다.
편집하다:작업 링크 프로그래밍의 기초 pdf.텍스트에서 "예외"를 검색해 보세요.
다른 팁
초기 예외와 함께 새 예외가 발생하면 초기 스택 추적도 유지됩니다.
try{
}
catch(Exception ex){
throw new MoreDescriptiveException("here is what was happening", ex);
}
실제로는 몇 가지 상황이 있습니다. throw
명령문은 StackTrace 정보를 유지하지 않습니다.예를 들어, 아래 코드에서:
try
{
int i = 0;
int j = 12 / i; // Line 47
int k = j + 1;
}
catch
{
// do something
// ...
throw; // Line 54
}
StackTrace는 47행에서 예외가 발생했지만 54행에서 예외가 발생했음을 나타냅니다.
Unhandled Exception: System.DivideByZeroException: Attempted to divide by zero.
at Program.WithThrowIncomplete() in Program.cs:line 54
at Program.Main(String[] args) in Program.cs:line 106
위에서 설명한 것과 같은 상황에서는 원본 StackTrace를 보존하는 두 가지 옵션이 있습니다.
Exception.InternalPreserveStackTrace 호출
비공개 메서드이므로 리플렉션을 사용하여 호출해야 합니다.
private static void PreserveStackTrace(Exception exception)
{
MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace",
BindingFlags.Instance | BindingFlags.NonPublic);
preserveStackTrace.Invoke(exception, null);
}
StackTrace 정보를 보존하기 위해 비공개 방법을 사용한다는 단점이 있습니다.이는 .NET Framework의 이후 버전에서 변경될 수 있습니다.위의 코드 예제와 아래 제안된 솔루션은 다음에서 추출되었습니다. Fabrice MARGUERIE 웹로그.
Exception.SetObjectData 호출
아래 기술은 다음과 같이 제안되었습니다. 안톤 티키 에 대한 대답으로 C#에서 스택 추적을 잃지 않고 InnerException을 다시 발생시키는 방법은 무엇입니까? 질문.
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
}
하지만 공개 메서드에만 의존한다는 장점이 있지만 다음 예외 생성자에도 의존합니다(제3자가 개발한 일부 예외는 구현하지 않음).
protected Exception(
SerializationInfo info,
StreamingContext context
)
내 상황에서는 내가 사용하고 있던 타사 라이브러리에서 발생한 예외가 이 생성자를 구현하지 않았기 때문에 첫 번째 접근 방식을 선택해야 했습니다.
때를 throw ex
, 본질적으로 새로운 예외가 발생하고 원래 스택 추적 정보가 누락됩니다. throw
선호되는 방법입니다.
경험 법칙은 기본을 잡고 던지는 것을 피하는 것입니다. Exception
물체.이렇게 하면 예외에 대해 좀 더 현명해지게 됩니다.즉, 당신은 SqlException
귀하의 처리 코드가 NullReferenceException
.
현실 세계에서는 잡기도 하지만 그리고 로깅 기본 예외도 좋은 습관이지만 예외를 얻으려면 모든 과정을 수행하는 것을 잊지 마십시오. InnerExceptions
그랬을 수도 있습니다.
아무도 그 차이점을 설명하지 않았습니다. ExceptionDispatchInfo.Capture( ex ).Throw()
그리고 평원 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와 유사하지만 원래 예외의 유형을 변경하므로 실제 다시 발생은 아닙니다.
몇몇 사람들은 실제로 매우 중요한 점을 놓쳤습니다. 'throw'와 'throw ex'는 동일한 작업을 수행할 수 있지만 예외가 발생한 라인인 중요한 정보를 제공하지 않습니다.
다음 코드를 고려해보세요.
static void Main(string[] args)
{
try
{
TestMe();
}
catch (Exception ex)
{
string ss = ex.ToString();
}
}
static void TestMe()
{
try
{
//here's some code that will generate an exception - line #17
}
catch (Exception ex)
{
//throw new ApplicationException(ex.ToString());
throw ex; // line# 22
}
}
'throw' 또는 'throw ex'를 수행하면 스택 추적을 얻을 수 있지만 라인#은 #22가 되므로 정확히 어떤 라인이 예외를 발생시켰는지 알 수 없습니다(1개 또는 몇 개만 있는 경우 제외). try 블록의 코드 줄).예외에서 예상되는 라인 #17을 얻으려면 원래 예외 스택 추적을 사용하여 새 예외를 발생시켜야 합니다.
항상 "Throw"를 사용해야합니다. .NET의 예외를 재창조하기 위해
이것을 참고하세요,http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx
기본적으로 MSIL(CIL)에는 "throw"와 "rethrow"라는 두 가지 명령이 있습니다.
- C#의 "Throw Ex;" MSIL의 "Throw"로 컴파일됩니다.
- C#'s "Throw"; -Sil "Rethrow"로!
기본적으로 "throw ex"가 스택 추적을 무시하는 이유를 알 수 있습니다.
다음을 사용할 수도 있습니다.
try
{
// Dangerous code
}
finally
{
// clean up, or do nothing
}
그리고 발생한 모든 예외는 이를 처리하는 다음 수준까지 버블링됩니다.
나는 확실히 다음을 사용할 것입니다 :
try
{
//some code
}
catch
{
//you should totally do something here, but feel free to rethrow
//if you need to send the exception up the stack.
throw;
}
그러면 스택이 보존됩니다.
참고로 나는 이것을 테스트하고 'Throw'로보고 된 스택 추적을 테스트했다. 완전히 올바른 스택 트레이스가 아닙니다.예:
private void foo()
{
try
{
bar(3);
bar(2);
bar(1);
bar(0);
}
catch(DivideByZeroException)
{
//log message and rethrow...
throw;
}
}
private void bar(int b)
{
int a = 1;
int c = a/b; // Generate divide by zero exception.
}
스택 추적은 예외의 원인을 올바르게 가리키지만(보고된 줄 번호) foo()에 대해 보고된 줄 번호는 throw된 줄입니다.따라서 bar()에 대한 호출 중 어느 호출이 예외를 발생시켰는지 알 수 없습니다.