.NET 예외를 포착하고 다시 발생시키는 모범 사례


284

예외를 잡아서 다시 던질 때 고려해야 할 모범 사례는 무엇입니까? Exception객체 InnerException와 스택 추적이 유지 되도록하고 싶습니다 . 다음 코드 블록이이를 처리하는 방식에 차이가 있습니까?

try
{
    //some code
}
catch (Exception ex)
{
    throw ex;
}

대 :

try
{
    //some code
}
catch
{
    throw;
}

답변:


262

스택 추적을 유지하는 방법은 throw;This is valid of the

try {
  // something that bombs here
} catch (Exception ex)
{
    throw;
}

throw ex;기본적으로 그 시점에서 예외를 던지는 것과 같으므로 스택 추적은 throw ex;명령문을 발행하는 위치로만 이동 합니다.

예외가 예외를 전달할 수 있다고 가정하면 Mike 도 정확합니다 (권장).

Karl Seguin프로그래밍 전자 책 기초 에서 예외 처리 에 대한 을 많이 썼습니다 .

편집 : 프로그래밍 기초에 대한 작업 링크 pdf. 텍스트에서 "예외"를 검색하십시오.


10
글쓰기가 훌륭하다면 잘 모르겠습니다. {// ...} catch (Exception ex) {throw new Exception (ex.Message + "other stuff"); } 좋습니다. 문제는 당신이 더-에는 모든 예외, 큰 잡을없는 경우를 제외하고, 스택까지 더 그 예외를 처리 할 수 완전히없는 것입니다 (당신은 당신이 그에서 OutOfMemoryException을 처리하지 않으려는?)
LJS

2
@ljs 그가 추천 한 섹션이 보이지 않기 때문에 귀하의 의견 이후 기사가 변경 되었습니까? 실제로는 정반대이며, 그렇게하지 말라고하고 OutOfMemoryException도 처리 할 것인지 묻습니다!
RyanfaeScotland

6
때때로 던져; 스택 추적을 보존하기에 충분하지 않습니다. 다음은 https://dotnetfiddle.net/CkMFoX
Artavazd Balayan

4
또는 ExceptionDispatchInfo.Capture(ex).Throw(); throw;.NET +4.5 stackoverflow.com/questions/57383/…
Alfred Wallace

@AlfredWallace 솔루션은 완벽하게 작동했습니다. {...} catch를 시도하십시오 {throw}가 스택 추적을 유지하지 못했습니다. 감사합니다.
atownson 2016 년

100

초기 예외와 함께 새 예외를 throw하면 초기 스택 추적도 유지됩니다.

try{
} 
catch(Exception ex){
     throw new MoreDescriptiveException("here is what was happening", ex);
}

내가 무엇을 시도해도 새로운 예외 ( "메시지", ex)는 항상 ex를 throw하고 사용자 정의 메시지를 무시합니다. new Exception ( "message", ex.InnerException) throw하지만 작동합니다.
Tod

사용자 지정 예외가 필요하지 않으면 AggregateException (.NET 4+) msdn.microsoft.com/en-us/library/…를
Nikos Tsokos

AggregateException집계 된 작업에 대한 예외에만 사용해야합니다. 예를 들어 CLR 의 ParallelEnumerableTask클래스에 의해 발생합니다 . 사용법은 아마도이 예제를 따라야합니다.
Aluan Haddad

29

실제로,이 throw통계로 인해 StackTrace 정보가 보존되지 않는 상황이 있습니다. 예를 들어 아래 코드에서

try
{
  int i = 0;
  int j = 12 / i; // Line 47
  int k = j + 1;
}
catch
{
  // do something
  // ...
  throw; // Line 54
}

StackTrace는 줄 54에서 예외가 발생했지만 줄 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 호출

아래의 기술은 Anton TykhyyC #에 대한 답변 으로 제안한 것으로 스택 추적 질문 을 잃지 않고 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
)

필자의 상황에서는 사용중인 타사 라이브러리에서 발생한 예외 가이 생성자를 구현하지 않았기 때문에 첫 번째 접근 방식을 선택해야했습니다.


1
예외를 잡아 원하는 곳 어디에서나이 예외를 게시 할 수 있습니다. 그런 다음 사용자에게 일어난 일을 설명하는 새로운 것을 던지십시오. 이렇게하면 현재 예외가 발생했을 때 발생한 상황을 볼 수 있으며 사용자는 실제 예외가 무엇인지 부주의하게 처리 할 수 ​​있습니다.
Çöđěxěŕ

2
.NET 4.5에는 세 번째 옵션이 있습니다. ExceptionDispatchInfo를 사용하십시오. 여기에 관련된 질문에 비극의 답변을 참조하십시오 : stackoverflow.com/a/17091351/567000을 추가 정보를 원하시면.
Søren Boisen

20

의 경우 throw ex본질적으로 새로운 예외가 발생하며 원래 스택 추적 정보가 누락됩니다. throw선호되는 방법입니다.


13

경험상 기본 Exception개체를 잡거나 던지는 것을 피하는 것이 좋습니다. 이를 통해 예외에 대해 조금 더 똑똑해집니다. 다른 말로하면 SqlException처리 코드가 문제가되지 않도록 명시 적으로 잡아야합니다 .NullReferenceException .

그러나 실제 상황에서는 기본 예외를 포착 하고 기록 하는 것도 좋은 방법이지만 모든 것을 얻기 위해 걸어 다니는 것을 잊지 마십시오 InnerExceptions.


2
AppDomain.CurrentDomain.UnhandledException 및 Application.ThreadException 예외를 사용하여 로깅 목적으로 처리되지 않은 예외를 처리하는 것이 가장 좋습니다. 어디서나 큰 시도 {...} catch (Exception ex) {...}를 사용하면 많은 중복이 발생합니다. 처리 된 예외를 기록할지 여부에 따라 (최소한 최소) 중복이 불가피 할 수 있습니다.
ljs 2016 년

게다가 당신이 그 사건 수단 사용 당신이 큰 팔자 '시도 {...} 캐치 (예외 예) {...} 블록을 사용하는 경우 반면에, 모든 처리되지 않은 예외를 기록하는 것은 몇 가지를 놓칠 수 있습니다.
ljs 2016 년

10

항상 "throw"를 사용해야합니다. .NET에서 예외를 다시 발생시키기 위해

이것을 참조하십시오 http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx

기본적으로 MSIL (CIL)에는 "throw"와 "rethrow"라는 두 가지 명령이 있습니다.

  • C #의 "전 던지기;" MSIL의 "throw"로 컴파일
  • C #의 "throw;" -MSIL에 "다시 던져"!

기본적으로 "throw ex"가 스택 추적을 재정의하는 이유를 알 수 있습니다.


링크-실제로 , 인용을 제공하는 출처 -는 좋은 정보로 가득 차 throw ex;있으며 Java에서 많은 사람들이 다시 생각할 이유에 대한 가능한 범인을 지적합니다 ! 그러나 A 등급 답변을 받으려면 여기해당 정보를 포함시켜야합니다 . (여전히 ExceptionDispatchInfo.Capturejeuoekdcwzfwccu답변을 따라 잡고 있습니다.)
ruffin

10

아무도 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.
    }
}

삼.

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 ex;이것이 가장 좋은 답변입니다.
NH.

8

몇몇 사람들은 실제로 매우 중요한 요점을 놓쳤습니다. '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가되므로 정확히 어느 줄이 예외를 던지고 있는지 알 수 없습니다 try 블록의 코드 줄). 예외에서 예상되는 # 17 행을 얻으려면 원래 예외 스택 추적으로 새 예외를 발생시켜야합니다.


3

다음을 사용할 수도 있습니다.

try
{
// Dangerous code
}
finally
{
// clean up, or do nothing
}

그리고 던져진 예외는 그 예외를 처리하는 다음 수준으로 올라갑니다.


3

나는 확실히 사용할 것이다 :

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

스택이 보존됩니다.


1
2008 년에 저를 공평하게하기 위해 OP는 스택을 보존하는 방법을 묻고있었습니다. 그리고 2008 년은 정답을주었습니다. 내 대답에서 누락 된 것은 실제로 캐치에서 무언가를하는 부분입니다.
1kevgriff

@JohnSaunders 이전에 아무것도 하지 않은 경우에만 해당됩니다 throw. 예를 들어, 일회용을 정리하고 (오류에서만 호출) 예외를 던질 수 있습니다.
Meirion Hughes

@meirion 내가 의견을 쓸 때 던지기 전에 아무것도 없었습니다. 그것이 추가되었을 때, 나는 upvoted했지만 주석을 삭제하지 않았습니다.
John Saunders

0

참고로 나는 이것을 테스트했고 스택 추적은 '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 ()에 대한 호출 중 어떤 예외가 발생했는지 알 수 없습니다.


그렇기 때문에 예외를 처리하려고 계획하지 않는 한 예외를 포착하지 않는 것이 가장 좋습니다.
Nate Zaugg
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.