한 번에 여러 예외를 잡으시겠습니까?


2140

단순히 잡는 것은 바람직하지 않습니다 System.Exception. 대신 "알려진"예외 만 포착해야합니다.

이제는 때때로 불필요한 반복 코드로 이어집니다. 예를 들면 다음과 같습니다.

try
{
    WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
    WebId = Guid.Empty;
}
catch (OverflowException)
{
    WebId = Guid.Empty;
}

궁금합니다 : 두 예외를 모두 잡아서 WebId = Guid.Empty한 번만 전화를 걸 수있는 방법이 있습니까?

주어진 예제는 단지이므로 간단합니다 GUID. 그러나 객체를 여러 번 수정하는 코드를 상상하고 조작 중 하나가 예상대로 실패하면을 "재설정"하려고합니다 object. 그러나 예기치 않은 예외가있는 경우 여전히 더 높이 던지고 싶습니다.


5
.net 4 이상을 사용하는 경우 집계
예외

2
Bepenfriends- System.GuidAggregateException을 발생 시키지 않기 때문에 , 당신 (혹은 누군가)이 그것을 AggregateException 등에 랩핑하는 방법을 보여주는 답변을 게시 할 수 있다면 좋을 것입니다.
weir

1

11
"System.Exception을 간단히 잡는 것은 좋지 않습니다." 그리고 메소드가 32 가지 유형의 예외를 던질 수 있다면 어떻게됩니까? 각각 개별적으로 캐치를 작성 하시겠습니까?
giorgim

5
메소드가 32 가지 유형의 예외를 던지면 잘못 작성되었습니다. 자체 호출에서 수행하는 예외를 포착하지 않거나, 한 가지 방법으로 FAR을 너무 많이 수행하거나, 32 개 중 대다수 / 모두가 이유 코드가있는 단일 예외 여야합니다.
Flynn1179

답변:


2100

캐치 System.Exception및 유형 켜기

catch (Exception ex)            
{                
    if (ex is FormatException || ex is OverflowException)
    {
        WebId = Guid.Empty;
        return;
    }

    throw;
}

69
불행히도 FxCop (예 : Visual Studio Code Analysis)은 예외를 잡을 때 좋아하지 않습니다.
앤드류 개리슨

15
예외를 catch하지 않는 데 동의하지만이 경우 catch는 필터입니다. 다른 예외 유형을 처리하는 계층이 더 높을 수 있습니다. catch (Exception x)가 포함되어 있지만 이것이 정확하다고 말하고 싶습니다. 프로그램 흐름을 수정하지 않고 특정 예외를 처리 한 다음 나머지 응용 프로그램이 다른 예외 유형을 처리하도록합니다.
lkg

28
위 코드를 사용할 때 최신 버전의 FxCop에서 예외가 발생하지 않습니다.
Peter

28
우선 OP의 코드에 어떤 문제가 있는지 확실하지 않습니다. # 1 허용 된 답변은 거의 두 배 많은 행이며 읽기가 훨씬 어렵습니다.
João Bragança

22
@ JoãoBragança :이 예제 의이 대답은 더 많은 행을 사용하지만 예를 들어 파일 IO를 처리하는지 상상해보십시오. 예외를 포착하고 로그 메시징을 수행하기 만하면됩니다. 파일 IO 방법. 그런 다음 더 많은 수 (약 5 개 이상)의 다른 유형의 예외를 처리해야합니다. 이 상황에서이 방법을 사용하면 줄을 절약 할 수 있습니다.
Xilconic

594

편집 : C # 6.0에서 예외 필터는 이제 완벽하게 갈 수있는 방법이라고 말하는 사람들과 동의합니다.catch (Exception ex) when (ex is ... || ex is ... )

내가 여전히 한 줄짜리 레이아웃을 싫어하고 개인적으로 다음과 같이 코드를 배치한다는 것을 제외하고. 나는 이것이 그것이 이해력을 향상 시킨다고 생각하기 때문에 그것이 미학적 인 것처럼 기능적이라고 생각한다. 일부는 동의하지 않을 수 있습니다.

catch (Exception ex) when (
    ex is ...
    || ex is ...
    || ex is ...
)

기발한:

나는 여기서 파티에 조금 늦었지만 성스러운 연기는 ...

추격을 바로 잡기 위해이 종류의 이전 답변을 복제하지만 실제로 몇 가지 예외 유형에 대해 공통 작업을 수행하고 한 방법의 범위 내에서 모든 것을 깔끔하고 깔끔하게 유지하려면 람다를 사용하지 않는 이유는 무엇입니까? 다음과 같은 작업을 수행하는 / closure / inline 함수? 내 말은, 당신은 당신이 그 폐쇄를 당신이 모든 곳에서 활용할 수있는 별도의 방법으로 만들고 싶다는 것을 깨닫게 될 가능성이 꽤 있다는 것을 의미합니다. 그러나 실제로 나머지 코드를 구조적으로 변경하지 않고 그렇게하는 것이 매우 쉽습니다. 권리?

private void TestMethod ()
{
    Action<Exception> errorHandler = ( ex ) => {
        // write to a log, whatever...
    };

    try
    {
        // try some stuff
    }
    catch ( FormatException  ex ) { errorHandler ( ex ); }
    catch ( OverflowException ex ) { errorHandler ( ex ); }
    catch ( ArgumentNullException ex ) { errorHandler ( ex ); }
}

나는 지구상에서 기본적으로 다음을 대체하기 위해이 모든 노력을 기울이는 이유를 궁금해 할 수는 없습니다 ( 경고 : 약간의 아이러니 / 풍자).

try
{
    // try some stuff
}
catch( FormatException ex ){}
catch( OverflowException ex ){}
catch( ArgumentNullException ex ){}

...이 다음 코드 냄새의 미친 변형으로, 예를 들어, 몇 번의 키 입력을 저장하고 있다고 가정합니다.

// sorta sucks, let's be honest...
try
{
    // try some stuff
}
catch( Exception ex )
{
    if (ex is FormatException ||
        ex is OverflowException ||
        ex is ArgumentNullException)
    {
        // write to a log, whatever...
        return;
    }
    throw;
}

확실히 자동으로 더 읽기 쉽지 않기 때문입니다.

물론, /* write to a log, whatever... */ return;첫 번째 예 에서 세 개의 동일한 인스턴스를 남겼습니다 .

그러나 그것은 내 요점입니다. 여러분은 기능 / 방법에 대해 들어 보았습니다. 진심으로. 공통 ErrorHandler함수를 작성하고 각 catch 블록에서 호출하십시오.

나에게 묻는다면 두 번째 예제 ( ifis키워드 포함)는 프로젝트의 유지 보수 단계에서 읽기가 쉽지 않으며 오류가 발생하기 쉽습니다.

유지 보수 단계는 프로그래밍에 익숙하지 않은 사용자를 위해 프로젝트 전체 수명의 98.7 % 이상을 차지할 것이며 유지 보수를 수행하는 불량 Schmuck은 거의 다른 사람이 될 것입니다. 그리고 그들이 당신의 이름을 저주하는 일에 그들의 시간의 50 %를 소비 할 가능성이 매우 높습니다.

그리고 물론의 FxCop 당신의 당신이 할 필요가 있으므로 껍질 정확하게 실행중인 프로그램을 할 압축 한 코드에 속성을 추가 한 경우 99.9 %에 완전히이라고 문제를 무시의 FxCop 말씀 만이 신고가 정확합니다. 그리고, 죄송합니다, 착각 할 수도 있지만, 그 "무시"속성이 실제로 앱에 컴파일되지 않습니까?

전체 if테스트를 한 줄에 배치하면 더 읽기 쉬워 집니까? 나는 그렇게 생각하지 않습니다. 한 번에 한 줄에 더 많은 코드를 넣으면 "더 빠르게"실행될 것이라고 한 번도 다른 프로그래머가 격렬하게 주장했음을 의미합니다. 그러나 물론 그는 굶주린 견과였다. 통역사 또는 컴파일러가 어떻게 긴 줄을 별개의 한 줄에 한 줄로 나누는 방법을 그에게 설명하려고 노력했지만 본질적으로 결과가 동일한 경우 컴파일러를 능숙하게 만들지 않고 코드를 읽을 수있게 만들었습니다. 그러나 나는 산만하다.

한 달에서 두 달 사이에 예외 유형을 세 개 더 추가 할 때 읽기가 훨씬 어렵 습니까? (답 : 읽기 가 훨씬 어렵습니다).

실제로 중요한 점 중 하나는 우리가 매일보고있는 텍스트 소스 코드를 형식화하는 대부분의 포인트가 코드 실행시 실제로 발생하는 일을 다른 사람에게 실제로 분명하게 만드는 것입니다. 컴파일러는 소스 코드를 완전히 다른 것으로 변환하고 코드 서식 스타일에 대해서는 신경 쓰지 않기 때문입니다. 따라서 올인원 라인도 완전히 짜증납니다.

그냥 ...

// super sucks...
catch( Exception ex )
{
    if ( ex is FormatException || ex is OverflowException || ex is ArgumentNullException )
    {
        // write to a log, whatever...
        return;
    }
    throw;
}

36
내가이 질문을 처음 발견했을 때 나는 받아 들여진 대답을 끝냈습니다. 차갑게 모든 것을 잡을 수 Exception있고 유형을 확인하십시오. 코드가 정리되었다고 생각했지만 질문으로 다시 돌아와 실제로 질문에 대한 다른 답변을 읽었습니다. 나는 잠시 동안 씹었지만 당신과 동의해야합니다. 모든 것을 포착하고, 목록과 비교하여 유형을 확인하고, 코드를 래핑하고 던지는 것보다 코드를 건조시키는 기능을 사용하는 것이 읽기 쉽고 유지 관리가 쉽습니다. 늦게 와서 대안 및 제정신 (IMO) 옵션을 제공해 주셔서 감사합니다. +1.
오류

8
를 포함하려는 경우 오류 처리 기능을 사용하면 작동하지 않습니다 throw;. 각 catch 블록에서 해당 코드 줄을 반복해야합니다 (물론 세계의 끝은 아니지만 반복 해야하는 코드이므로 언급 할 가치가 있습니다).
kad81

5
@ kad81, 그건 사실이지만 로깅 및 정리 코드를 한곳에 작성하고 필요한 경우 한곳에서 변경하면 기본 예외 유형을 잡은 다음 구겨진 기본 예외 유형을 잡는 구피 의미없이 한곳에서 변경하는 이점을 얻을 수 있습니다 예외 유형 그리고 throw();각 catch 블록에서 하나의 추가 진술은 지불해야 할 작은 가격, IMO이며 필요한 경우 추가 예외 유형별 정리를 수행 할 수있는 위치에 남아 있습니다.
Craig

2
안녕 @Reitffunk, Func<Exception, MyEnumType>대신에 사용하십시오 Action<Exception>. 즉 Func<T, Result>, Result반환 유형입니다.
Craig

3
나는 여기에 완전히 동의하고 있습니다. 나도 첫 번째 답변을 읽고 생각이 논리적 인 것 같습니다. 모든 예외 처리기의 일반 1로 이동했습니다. 내 안에 뭔가가 내부적으로 말끔하게 만들었습니다 ... 그래서 코드를 되돌 렸습니다. 그런 다음이 아름다움을 발견했습니다! 이 필요 허용 대답으로
코너 갤러거에게

372

다른 사람들이 지적했듯이 ifcatch 블록 내에 명령문을 사용하여 진행 상황을 결정할 수 있습니다. C # 6은 예외 필터를 지원하므로 다음이 작동합니다.

try {  }
catch (Exception e) when (MyFilter(e))
{
    
}

그러면 MyFilter메소드는 다음과 같이 보일 수 있습니다.

private bool MyFilter(Exception e)
{
  return e is ArgumentNullException || e is FormatException;
}

또는 이것은 모두 인라인으로 수행 될 수 있습니다 (when 문의 오른쪽은 부울 표현식이어야합니다).

try {  }
catch (Exception e) when (e is ArgumentNullException || e is FormatException)
{
    
}

이것은 블록 if내에서 명령문 을 사용하는 것과 다릅니다. catch예외 필터 사용 하면 스택이 풀리지 않습니다 .

Visual Studio 2015 를 다운로드 하여 확인할 수 있습니다 .

Visual Studio 2013을 계속 사용하려면 다음과 같은 너겟 패키지를 설치할 수 있습니다.

설치 패키지 Microsoft.Net.Compilers

작성 당시에는 C # 6에 대한 지원이 포함됩니다.

이 패키지를 참조하면 시스템에 설치된 모든 버전과 달리 패키지에 포함 된 특정 버전의 C # 및 Visual Basic 컴파일러를 사용하여 프로젝트가 빌드됩니다.


3
6의 공식 릴리스를 참을성있게 기다리는 중 ... 나는 이것이 일어날 때 이것이 확인되는 것을보고 싶다.
RubberDuck

@RubberDuck C # 6의 null 전파 연산자로 죽어 가고 있습니다. 언어 / 컴파일러의 불안정한 위험이 그만한 가치가 있음을 다른 팀원에게 설득하려고합니다. 큰 영향을 미치는 많은 사소한 개선. 사람들이 이것이 유언장을 인식하고 가능한 한, 답변으로 표시되는 것은 중요하지 않습니다.
Joe

권리?! 가까운 시일 내에 코드베이스를 잘 살펴볼 것입니다. =) 수표가 중요하지 않다는 것을 알고 있지만 승인 된 답변이 곧 구식이 될 것이므로 OP 가이를 확인하여 적절한 가시성을 제공하기를 기대합니다.
RubberDuck

그것이 부분적으로 아직 @Joe에게 수여하지 않은 이유입니다. 나는 이것이 보이기를 원한다. 명확성을 위해 인라인 필터의 예를 추가 할 수 있습니다.
RubberDuck

188

불행히도 C #에는 그렇지 않습니다. 예외 필터가 필요하고 C #은 MSIL의 해당 기능을 노출하지 않기 때문입니다. VB.NET은이 기능을 가지고 있습니다.

Catch ex As Exception When TypeOf ex Is FormatException OrElse TypeOf ex Is OverflowException

당신이 할 수있는 일은 익명 함수를 사용하여 오류가 발생한 코드를 캡슐화 한 다음 특정 catch 블록에서 호출하는 것입니다.

Action onError = () => WebId = Guid.Empty;
try
{
    // something
}
catch (FormatException)
{
    onError();
}
catch (OverflowException)
{
    onError();
}

26
흥미로운 아이디어와 VB.net이 때때로 C #에 비해 몇 가지 흥미로운 이점을 가지고 있다는 또 다른 예
Michael Stum

47
@MichaelStum은 그런 종류의 문법으로 전혀 흥미롭지 않을 것입니다 ... shudder
MarioDS

17
예외 필터가 C # 6에 나옵니다! rethrowing에 찬성 필터를 사용하는 차이가 있습니다 roslyn.codeplex.com/discussions/541301
아르네 Deruwe

@ArneDeruwe 해당 링크에 감사드립니다! 난 그냥 다시 던져하지 않는 또 하나의 중요한 이유를 알게 : throw e;파괴한다 스택 트레이스 호출 스택, throw;파괴한다 "전용"호출 스택은 A (쓸모없는 충돌이-덤프 렌더링!) 매우 좋은 이유 사용도 그것을 피할 수있는 경우를!
AnorZaken

1
C # 6부터 예외 필터를 사용할 수 있습니다! 드디어.
Danny

134

완전성을 위해 .NET 4.0 이후 코드는 다음과 같이 다시 작성할 수 있습니다.

Guid.TryParse(queryString["web"], out WebId);

TryParse 는 예외가 발생하지 않으며 형식이 잘못된 경우 WebId를로 설정하여 false를 반환합니다 Guid.Empty.


C # 7 부터는 별도의 행에 변수를 도입하지 않아도됩니다.

Guid.TryParse(queryString["web"], out Guid webId);

.NET Framework에서는 아직 버전 4.6에서는 사용할 수없는 반환 튜플을 구문 분석하는 메서드를 만들 수도 있습니다.

(bool success, Guid result) TryParseGuid(string input) =>
    (Guid.TryParse(input, out Guid result), result);

그리고 이것을 다음과 같이 사용하십시오 :

WebId = TryParseGuid(queryString["web"]).result;
// or
var tuple = TryParseGuid(queryString["web"]);
WebId = tuple.success ? tuple.result : DefaultWebId;

이 쓸모없는 답변에 대한 다음 쓸모없는 업데이트는 C # 12에서 매개 변수의 해체가 구현 될 때 발생합니다.


19
정확하고 간결하며 예외 처리의 성능 저하, 의도적으로 프로그램 흐름을 제어하기 위해 예외를 사용하는 잘못된 형식, 변환 논리가 분산되는 소프트 포커스, 여기 저기 약간 .
Craig

9
나는 당신이 무엇을 의미하는지 알고 있지만, 물론 Guid.TryParse결코 반환하지 않습니다 Guid.Empty. 문자열의 형식이 올바르지 않으면 result출력 매개 변수를로 설정 Guid.Empty하지만을 리턴합니다 false . 내가의 스타일로 일을 코드를 본 적이 있기 때문에 나는 그것을 언급하고있어 Guid.TryParse(s, out guid); if (guid == Guid.Empty) { /* handle invalid s */ }경우에 일반적으로 잘못, s의 문자열 표현 될 수를 Guid.Empty.

14
와우 당신은 질문의 정신에 있지 않다는 것을 제외하고는 질문에 대답했습니다. 더 큰 문제는 다른 것입니다 :(
nawfal

6
물론 TryParse를 사용하는 적절한 패턴은 더 비슷 if( Guid.TryParse(s, out guid){ /* success! */ } else { /* handle invalid s */ }합니다. 입력 값이 실제로 Guid의 문자열 표현 일 수있는 깨진 예제와 같이 모호하지 않습니다.
Craig

2
이 답변은 Guid.Parse와 관련하여 실제로 정확할 수도 있지만, 원래 질문의 요점을 모두 놓쳤습니다. Guid.Parse와는 아무런 관련이 없지만 Exception vs FormatException / OverflowException 등을 잡는 것과 관련이 있습니다.
코너 갤러거

114

예외 필터는 이제 C # 6+에서 사용할 수 있습니다. 넌 할 수있어

try
{
       WebId = new Guid(queryString["web"]);
}
catch (Exception ex) when(ex is FormatException || ex is OverflowException)
{
     WebId = Guid.Empty;
}

C # 7.0 이상에서는이를 패턴 일치와 결합 할 수도 있습니다

try
{
   await Task.WaitAll(tasks);
}
catch (Exception ex) when( ex is AggregateException ae &&
                           ae.InnerExceptions.Count > tasks.Count/2)
{
   //More than half of the tasks failed maybe..? 
}

이 방법은 간단하고 명확하기 때문에 선호 될뿐만 아니라 조건이 충족되지 않으면 스택을 풀지 않아도되므로 다시 던지기에 비해 성능과 진단 정보가 향상됩니다.
joe

74

응용 프로그램을 C # 6으로 업그레이드 할 수 있다면 운이 좋습니다. 새로운 C # 버전은 예외 필터를 구현했습니다. 그래서 당신은 이것을 쓸 수 있습니다 :

catch (Exception ex) when (ex is FormatException || ex is OverflowException) {
    WebId = Guid.Empty;
}

어떤 사람들은이 코드가

catch (Exception ex) {                
    if (ex is FormatException || ex is OverflowException) {
        WebId = Guid.Empty;
    }
    throw;
}

그러나 그렇지 않습니다. 실제로 이것은 C # 6의 유일한 새로운 기능으로 이전 버전에서는 에뮬레이션 할 수 없습니다. 우선, 다시 던지기는 캐치를 건너 뛰는 것보다 더 많은 오버 헤드를 의미합니다. 둘째, 의미 상 동등하지 않습니다. 새로운 기능은 코드를 디버깅 할 때 스택을 그대로 유지합니다. 이 기능이 없으면 크래시 덤프는 덜 유용하거나 쓸모가 없습니다.

CodePlex에 대한 논의를 참조하십시오 . 그리고 차이점을 보여주는 예 입니다.


4
예외없이 throw하면 스택이 유지되지만 "ex를 던져"가 덮어 씁니다.
Ivan

32

당신이 사용하지 않을 경우 if내에서 문을 catch범위, 에에게 C# 6.0당신이 사용할 수있는 Exception Filters구문 이미 미리보기 버전의 CLR에 의해 지원 만 존재했다 VB.NET/ MSIL:

try
{
    WebId = new Guid(queryString["web"]);
}
catch (Exception exception) when (exception is FormatException || ex is OverflowException)
{
    WebId = Guid.Empty;
}

이 코드는 또는 일 Exception때만 잡습니다 .InvalidDataExceptionArgumentNullException

사실, 그 안에 기본적으로 어떤 조건이든 넣을 수 있습니다 when 절 :

static int a = 8;

...

catch (Exception exception) when (exception is InvalidDataException && a == 8)
{
    Console.WriteLine("Catch");
}

범위 if안에 있는 명령문 과 달리 catch, 다음 Exception Filters을 던질 수없고 Exceptions, 언제, 또는 조건이 아닌 true경우에는 다음을 수행합니다.catch 조건이 대신 평가됩니다.

static int a = 7;

static int b = 0;

...

try
{
    throw new InvalidDataException();
}
catch (Exception exception) when (exception is InvalidDataException && a / b == 2)
{
    Console.WriteLine("Catch");
}
catch (Exception exception) when (exception is InvalidDataException || exception is ArgumentException)
{
    Console.WriteLine("General catch");
}

출력 : 일반 캐치.

하나 이상이 있으면 true Exception Filter첫 번째 것이 허용됩니다.

static int a = 8;

static int b = 4;

...

try
{
    throw new InvalidDataException();
}
catch (Exception exception) when (exception is InvalidDataException && a / b == 2)
{
    Console.WriteLine("Catch");
}
catch (Exception exception) when (exception is InvalidDataException || exception is ArgumentException)
{
    Console.WriteLine("General catch");
}

출력 : 캐치.

당신이 볼 수 있듯이 MSIL코드로 번역되지 않은 if문장 만합니다 Filters, 그리고 Exceptions표시된 영역 내에서 던질 수 없다 Filter 1하고 Filter 2있지만,이 던지는 필터 Exception대신 실패도 마지막 비교 값은 전에 스택에 푸시 endfilter명령 필터의 성공 / 실패를 결정합니다 ( Catch 1 XOR Catch 2 이 그에 따라 실행 됨).

예외 필터 MSIL

또한 구체적 Guid으로 Guid.TryParse방법이 있습니다.


필터 일 때 다중 표시 및 다중 필터 사용시 발생하는 상황에 대한 설명 제공 +1
steven87vt

26

C # 7 을 사용하면 스위치 명령문의 가독성을 유지하면서 Michael Stum의 답변을 향상시킬 수 있습니다.

catch (Exception ex)
{
    switch (ex)
    {
        case FormatException _:
        case OverflowException _:
            WebId = Guid.Empty;
            break;
        default:
            throw;
    }
}

그리고 스위치 식으로 C # 8을 사용하면 다음과 같습니다.

catch (Exception ex)
{
    WebId = ex switch
    {
        _ when ex is FormatException || ex is OverflowException => Guid.Empty,
        _ => throw ex
    };
}

3
2018 IMHO 기준으로 답변이 적합해야합니다.
MemphiZ

6
Mat J의 대답 when은 스위치보다 훨씬 우아하고 적합합니다.
rgoliveira

@ rgoliveira : 질문에 대한 사례에 대해 Mat J의 대답이 더 우아하고 적절하다는 데 동의합니다. 그러나 예외 유형에 따라 실행하려는 다른 코드가 있거나 실제로 예외 인스턴스를 사용하려는 경우 읽기가 어렵습니다. 이 모든 시나리오는이 switch 문으로 동일하게 취급 될 수 있습니다.
Fabian

1
@Fabian "예외 유형에 따라 실행하려는 코드가 있거나 실제로 예외 인스턴스를 사용하려는 경우", 다른 catch블록을 만들 거나 어쨌든 캐스팅해야합니다. 내 경험상, throw;당신의 catch블록에있는 코드는 아마도 코드 냄새 일 것입니다.
rgoliveira

@rgoliveira : catch 블록에서 throw를 사용해도 괜찮습니다 . link를 참조 하십시오 . case 문은 실제로 패턴 일치 링크 를 사용하므로 폐기 연산자 링크 (밑줄)를 변수 이름으로 대체하면 캐스트 할 필요가 없습니다 . 저를 잘못 이해하지 마십시오. 예외 필터는 더 깔끔한 방법이지만 여러 캐치 블록은 중괄호를 많이 추가한다는 데 동의합니다.
Fabian

20

CodeAnalysis / FxCop을 제외하고는 허용되는 답변으로 보입니다. 이 일반적인 예외 유형을 포착하고 있다는 사실에 대해 불만을 제기 있습니다.

또한 "is"연산자는 성능을 약간 저하시킬 수 있습니다.

CA1800 : 불필요하게 캐스트하지 마십시오 " 'as'연산자의 결과 테스트 대신 그러나 그렇게하면 각 예외를 개별적으로 잡는 것보다 더 많은 코드를 작성하게된다.

어쨌든, 여기 내가 할 일이 있습니다 :

bool exThrown = false;

try
{
    // Something
}
catch (FormatException) {
    exThrown = true;
}
catch (OverflowException) {
    exThrown = true;
}

if (exThrown)
{
    // Something else
}

19
그러나 이렇게하면 스택 추적을 잃지 않고 예외를 다시 던질 수 없습니다. (승인 된 답변에 대한 Michael Stum의 의견 참조)
René

2
이 패턴은 예외를 저장하여 향상시킬 수 있습니다 (잘못된 형식을 변명하십시오-주석에 코드를 넣는 방법을 알 수 없습니다) : Exception ex = null; {// 무언가}를 시도하십시오 catch (FormatException e) {ex = e; } catch (OverflowException e) {ex = e; } if (ex! = null) {// 다른 것을 처리하고 ex를 처리}
Jesse Weigert

3
@JesseWeigert : 1. 백틱을 사용하여 텍스트에 단색 간격 글꼴과 밝은 회색 배경을 지정할 수 있습니다. 2. 여전히 stacktrace를 포함하여 원래 예외 를 다시 발생시킬 수 없습니다 .
Oliver

2
@CleverNeologism is연산자 를 사용하면 성능에 약간의 부정적인 영향을 줄 수 있지만 예외 처리기가 성능 최적화에 지나치게 관심을 갖지 않는 것도 사실입니다. 앱이 성능 최적화를 위해 예외 처리기에서 너무 많은 시간을 소비하는 경우 앱 성능에 큰 차이가있을 수 있지만 다른 코드 문제가 있습니다. 그럼에도 불구하고 스택 추적을 잃어 버리고 정리가 컨텍스트에서 catch 문에서 제거되기 때문에 여전히이 솔루션을 좋아하지 않습니다.
Craig

3
is운영자가 나중에 성능을 저하시키는 유일한 경우는 나중에 작업을 수행하는 as것입니다 (따라서 불필요하게 규칙을 규정합니다 ). 실제로 캐스트를 수행 할 필요없이 캐스트를 테스트하는 것이라면 is운영자가 정확히 원하는 것입니다.
saluce

19

C # 6에서 권장되는 방법은 예외 필터를 사용하는 것입니다. 예는 다음과 같습니다.

 try
 {
      throw new OverflowException();
 }
 catch(Exception e ) when ((e is DivideByZeroException) || (e is OverflowException))
 {
       // this will execute iff e is DividedByZeroEx or OverflowEx
       Console.WriteLine("E");
 }

18

이것은 Matt의 답변의 변형입니다 (이것이 조금 더 깨끗하다고 ​​생각합니다) ... 방법을 사용하십시오.

public void TryCatch(...)
{
    try
    {
       // something
       return;
    }
    catch (FormatException) {}
    catch (OverflowException) {}

    WebId = Guid.Empty;
}

다른 예외는 발생하고 코드 WebId = Guid.Empty;는 적중되지 않습니다. 다른 예외로 인해 프로그램이 중단되는 것을 원하지 않으면 다른 두 가지 캐치 후에 이것을 추가하십시오.

...
catch (Exception)
{
     // something, if anything
     return; // only need this if you follow the example I gave and put it all in a method
}

-1 WebId = Guid.Emtpy예외가 발생하지 않은 경우에 실행 됩니다.
Sepster

4
@ sepster 여기에 "// 뭔가"뒤의 return 문이 내포되어 있다고 생각합니다. 나는 해결책을 정말로 좋아하지 않지만, 이것은 토론에서 건설적인 변형입니다. downvote를 취소하려면 +1 :-)
toong

@Sepster toong이 맞습니다. 귀하가 거기에 반환을 원한다면 하나를 넣을 것이라고 가정했습니다 ... 나는 비슷하지만 정확한 질문이없는 다른 사람들이 다음과 같이 이익을 얻을 수있는 경우에 대비하여 모든 상황에 적용 할 수 있도록 일반적인 대답을하려고했습니다. 잘. 그러나 좋은 측정을 위해 return대답 에 a 를 추가했습니다 . 입력 해 주셔서 감사합니다.
bsara

18

Joseph Daigle의 답변 은 좋은 해결책이지만 다음 구조는 약간 깔끔하고 오류가 적은 경향이 있음을 알았습니다.

catch(Exception ex)
{   
    if (!(ex is SomeException || ex is OtherException)) throw;

    // Handle exception
}

식을 반전 시키면 몇 가지 장점이 있습니다.

  • 반환 진술은 필요하지 않습니다
  • 코드가 중첩되지 않았습니다
  • 요셉의 해결책에서 표현과 분리되어있는 '투구'또는 '반환'진술을 잊어 버릴 위험이 없습니다.

심지어 한 줄로 압축 할 수도 있습니다 (매우 예쁘지는 않지만)

catch(Exception ex) { if (!(ex is SomeException || ex is OtherException)) throw;

    // Handle exception
}

편집 : C # 6.0 의 예외 필터링 은 구문을 조금 더 깔끔하게 만들고 현재 솔루션에 비해 여러 가지 다른 이점 을 제공합니다. (주로 스택을 손상시키지 않은 채로 둡니다)

다음은 C # 6.0 구문을 사용하여 동일한 문제가 어떻게 보이는지 보여줍니다.

catch(Exception ex) when (ex is SomeException || ex is OtherException)
{
    // Handle exception
}

2
+1, 이것이 가장 좋은 답변입니다. return조건을 반전시키는 것도 약간 더 좋지만 대부분이 없기 때문에 가장 큰 대답 보다 낫습니다.
DCShannon

나는 그런 생각조차하지 않았다. 잘 잡았습니다. 목록에 추가하겠습니다.
Stefan T

16

@Micheal

약간 수정 된 코드 버전 :

catch (Exception ex)
{
   Type exType = ex.GetType();
   if (exType == typeof(System.FormatException) || 
       exType == typeof(System.OverflowException)
   {
       WebId = Guid.Empty;
   } else {
      throw;
   }
}

문자열 비교는 추악하고 느립니다.


21
왜 "is"키워드를 사용하지 않습니까?
Chris Pietschmann

29
@Michael-Microsoft가 FormatException에서 파생 된 StringTooLongException을 도입 한 경우 여전히 예외입니다. 단지 특정 예외입니다. '이 정확한 예외 유형을 잡으십시오'또는 '문자열 형식이 잘못되었음을 의미하는 예외를 잡으십시오'의 의미를 원하는지 여부에 따라 다릅니다.
Greg Beech

6
@Michael는 - 후자의 의미를 가지고 또한, 참고 캐치 (FormatException을 예) "고, 그것은 FormatException을에서 파생 아무것도 잡을 것입니다.
그렉 비치에게

14
"ex"가없는 @Alex No. "throw"는 원래 스택 추적을 포함하여 원래 예외를 발생시킵니다. "ex"를 추가하면 스택 추적이 재설정되므로 원래와 다른 예외가 발생합니다. 다른 사람이 나보다 더 잘 설명 할 수 있다고 확신합니다. :)
Samantha Branham

13
-1 :이 코드는 매우 취약 - 라이브러리 개발자는 대체 기대할 수 throw new FormatException();throw new NewlyDerivedFromFormatException();라이브러리를 사용하여 코드를 파괴하지 않고, 그것은 누군가가 사용 된 경우를 제외하고는 경우 처리하는 모든 예외 진정한 개최한다 ==대신 is(또는 간단히를 catch (FormatException)).
Sam Harwell

13

어때요?

try
{
    WebId = Guid.Empty;
    WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
}
catch (OverflowException)
{
}

Catch-Code를 Try-Block으로 완전히 옮길 수있는 경우에만 작동합니다. 그러나 객체를 여러 번 조작하고 중간에 하나의 조작이 실패하고 객체를 "재설정"하려는 이미징 코드.
Michael Stum

4
이 경우 재설정 기능을 추가하고 여러 catch 블록에서 호출합니다.
모리스

12
catch (Exception ex)
{
    if (!(
        ex is FormatException ||
        ex is OverflowException))
    {
        throw;
    }
    Console.WriteLine("Hello");
}

11

주의와 경고 : 또 다른 종류의 기능적 스타일.

링크에있는 내용이 귀하의 질문에 직접 대답하지는 않지만 다음과 같이 확장하기는 쉽지 않습니다.

static void Main() 
{ 
    Action body = () => { ...your code... };

    body.Catch<InvalidOperationException>() 
        .Catch<BadCodeException>() 
        .Catch<AnotherException>(ex => { ...handler... })(); 
}

(기본적으로 Catch자체 빈 반환 과부하를 제공 합니다)

이것에 대한 더 큰 질문은 이유 입니다. 나는 비용이 이익보다 크다고 생각하지 않는다. :)


1
이 접근법의 한 가지 가능한 장점은 예외를 포착하고 다시 던지는 것과 예외를 포착하지 않는 것 사이에 의미 상 차이가 있다는 것입니다. 어떤 경우에는 코드가 예외 포착 하지 않고 행동해야 합니다. vb.net에서는 가능하지만 vb.net으로 작성되고 C #에서 호출 된 래퍼를 사용 하지 않으면 C #에서는 불가능 합니다.
supercat

1
예외를 잡지 않고 어떻게 행동합니까? 나는 당신을 완전히 이해하지 못합니다.
nawfal

@nawful ... vb 필터 사용-함수 filt (ex 예외) : LogEx (ex) : return false ... 다음 캐치 라인에서 : catch ex when filt (ex)
FastAl

1
@FastAl 이것이 C # 6에서 예외 필터가 허용하는 것이 아닙니까?
HimBromBeere

@HimBromBeye 그래 그들은 직접 아날로그입니다
FastAl

9

2015-12-15 업데이트 : C # 6에 대해서는 https://stackoverflow.com/a/22864936/1718702 를 참조 하십시오 . 언어가 더 깨끗하고 표준입니다.

한 번만 포착하고 예외를 필터링 하는 보다 우아한 솔루션 을 원하는 사람들 을 위해 아래에 설명 된 확장 방법을 사용합니다.

나는 이미 다른 목적으로 작성된 내 라이브러리 에이 확장을 가지고 있었지만 type예외 를 확인하는 데 완벽하게 작동했습니다 . 게다가, imho, 그것은 많은 ||진술 보다 깨끗해 보입니다 . 또한 허용되는 답변과 달리 명시 적 예외 처리를 선호하므로 ex is ...파생 클래스가 부모 유형에 할당 될 수 있으므로 바람직하지 않은 동작이있었습니다).

용법

if (ex.GetType().IsAnyOf(
    typeof(FormatException),
    typeof(ArgumentException)))
{
    // Handle
}
else
    throw;

IsAnyOf.cs 확장 (종속성에 대한 전체 오류 처리 예 참조)

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter matches at least one of the passed in comparisons.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_comparisons">Values to compare against.</param>
        /// <returns>True if a match is found.</returns>
        /// <exception cref="ArgumentNullException"></exception>
        public static bool IsAnyOf<T>(this T p_parameter, params T[] p_comparisons)
        {
            // Validate
            p_parameter
                .CannotBeNull("p_parameter");
            p_comparisons
                .CannotBeNullOrEmpty("p_comparisons");

            // Test for any match
            foreach (var item in p_comparisons)
                if (p_parameter.Equals(item))
                    return true;

            // Return no matches found
            return false;
        }
    }
}

전체 오류 처리 예 (새 콘솔 앱에 복사하여 붙여 넣기)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Common.FluentValidation;

namespace IsAnyOfExceptionHandlerSample
{
    class Program
    {
        static void Main(string[] args)
        {
            // High Level Error Handler (Log and Crash App)
            try
            {
                Foo();
            }
            catch (OutOfMemoryException ex)
            {
                Console.WriteLine("FATAL ERROR! System Crashing. " + ex.Message);
                Console.ReadKey();
            }
        }

        static void Foo()
        {
            // Init
            List<Action<string>> TestActions = new List<Action<string>>()
            {
                (key) => { throw new FormatException(); },
                (key) => { throw new ArgumentException(); },
                (key) => { throw new KeyNotFoundException();},
                (key) => { throw new OutOfMemoryException(); },
            };

            // Run
            foreach (var FooAction in TestActions)
            {
                // Mid-Level Error Handler (Appends Data for Log)
                try
                {
                    // Init
                    var SomeKeyPassedToFoo = "FooParam";

                    // Low-Level Handler (Handle/Log and Keep going)
                    try
                    {
                        FooAction(SomeKeyPassedToFoo);
                    }
                    catch (Exception ex)
                    {
                        if (ex.GetType().IsAnyOf(
                            typeof(FormatException),
                            typeof(ArgumentException)))
                        {
                            // Handle
                            Console.WriteLine("ex was {0}", ex.GetType().Name);
                            Console.ReadKey();
                        }
                        else
                        {
                            // Add some Debug info
                            ex.Data.Add("SomeKeyPassedToFoo", SomeKeyPassedToFoo.ToString());
                            throw;
                        }
                    }
                }
                catch (KeyNotFoundException ex)
                {
                    // Handle differently
                    Console.WriteLine(ex.Message);

                    int Count = 0;
                    if (!Validate.IsAnyNull(ex, ex.Data, ex.Data.Keys))
                        foreach (var Key in ex.Data.Keys)
                            Console.WriteLine(
                                "[{0}][\"{1}\" = {2}]",
                                Count, Key, ex.Data[Key]);

                    Console.ReadKey();
                }
            }
        }
    }
}

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter matches at least one of the passed in comparisons.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_comparisons">Values to compare against.</param>
        /// <returns>True if a match is found.</returns>
        /// <exception cref="ArgumentNullException"></exception>
        public static bool IsAnyOf<T>(this T p_parameter, params T[] p_comparisons)
        {
            // Validate
            p_parameter
                .CannotBeNull("p_parameter");
            p_comparisons
                .CannotBeNullOrEmpty("p_comparisons");

            // Test for any match
            foreach (var item in p_comparisons)
                if (p_parameter.Equals(item))
                    return true;

            // Return no matches found
            return false;
        }

        /// <summary>
        /// Validates if any passed in parameter is equal to null.
        /// </summary>
        /// <param name="p_parameters">Parameters to test for Null.</param>
        /// <returns>True if one or more parameters are null.</returns>
        public static bool IsAnyNull(params object[] p_parameters)
        {
            p_parameters
                .CannotBeNullOrEmpty("p_parameters");

            foreach (var item in p_parameters)
                if (item == null)
                    return true;

            return false;
        }
    }
}

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter is not null, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <exception cref="ArgumentNullException"></exception>
        public static void CannotBeNull(this object p_parameter, string p_name)
        {
            if (p_parameter == null)
                throw
                    new
                        ArgumentNullException(
                        string.Format("Parameter \"{0}\" cannot be null.",
                        p_name), default(Exception));
        }
    }
}

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter is not null or an empty collection, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public static void CannotBeNullOrEmpty<T>(this ICollection<T> p_parameter, string p_name)
        {
            if (p_parameter == null)
                throw new ArgumentNullException("Collection cannot be null.\r\nParameter_Name: " + p_name, default(Exception));

            if (p_parameter.Count <= 0)
                throw new ArgumentOutOfRangeException("Collection cannot be empty.\r\nParameter_Name: " + p_name, default(Exception));
        }

        /// <summary>
        /// Validates the passed in parameter is not null or empty, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <exception cref="ArgumentException"></exception>
        public static void CannotBeNullOrEmpty(this string p_parameter, string p_name)
        {
            if (string.IsNullOrEmpty(p_parameter))
                throw new ArgumentException("String cannot be null or empty.\r\nParameter_Name: " + p_name, default(Exception));
        }
    }
}

두 개의 샘플 NUnit 단위 테스트

Exception유형에 대한 일치 동작 은 정확합니다 (예 : 자식은 부모 유형과 일치하지 않습니다).

using System;
using System.Collections.Generic;
using Common.FluentValidation;
using NUnit.Framework;

namespace UnitTests.Common.Fluent_Validations
{
    [TestFixture]
    public class IsAnyOf_Tests
    {
        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void IsAnyOf_ArgumentNullException_ShouldNotMatch_ArgumentException_Test()
        {
            Action TestMethod = () => { throw new ArgumentNullException(); };

            try
            {
                TestMethod();
            }
            catch (Exception ex)
            {
                if (ex.GetType().IsAnyOf(
                    typeof(ArgumentException), /*Note: ArgumentNullException derrived from ArgumentException*/
                    typeof(FormatException),
                    typeof(KeyNotFoundException)))
                {
                    // Handle expected Exceptions
                    return;
                }

                //else throw original
                throw;
            }
        }

        [Test, ExpectedException(typeof(OutOfMemoryException))]
        public void IsAnyOf_OutOfMemoryException_ShouldMatch_OutOfMemoryException_Test()
        {
            Action TestMethod = () => { throw new OutOfMemoryException(); };

            try
            {
                TestMethod();
            }
            catch (Exception ex)
            {
                if (ex.GetType().IsAnyOf(
                    typeof(OutOfMemoryException),
                    typeof(StackOverflowException)))
                    throw;

                /*else... Handle other exception types, typically by logging to file*/
            }
        }
    }
}

1
언어를 향상시키는 것이 "더 우세한" 것은 아닙니다 . 많은 곳에서 이것은 실제로 유지 관리 지옥을 만들었습니다. 몇 년 후, 많은 프로그래머들은 그들이 만든 괴물을 자랑스럽게 생각하지 않습니다. 그것은 당신이 읽는 데 사용되는 것이 아닙니다. "응?" 효과 또는 심지어 심각한 "WTF". 때로는 혼란 스럽습니다. 그것이하는 유일한 일은 나중에 유지 보수에서 코드를 다루어야하는 사람들을 위해 코드를 이해하기 어렵게 만드는 것입니다. 단 한 명의 프로그래머가 "영리한"사람이 되려고했기 때문입니다. 수년에 걸쳐, 나는 그 "영리한"솔루션이 좋은 것도 드물다는 것을 알게되었습니다.
Kaii

1
또는 몇 마디로 : 언어가 기본적으로 제공하는 가능성을 고수하십시오. 언어의 의미 좋아하지 않기 때문에 언어의 의미를 무시하려고 시도 하지 마십시오. 당신의 동료들 (그리고 미래의 나도)은 솔직히 감사 할 것입니다.
Kaii

또한 솔루션 when은의 모든 버전과 마찬가지로 C # 6의 의미 만 근사합니다 catch (Exception ex) {if (...) {/*handle*/} throw;}. 실제 값은 예외가 포착되기 전에when 필터가 실행 되므로 재 투사의 비용 / 스택 손상을 피할 수 있습니다. VB 및 MSIL에서만 이전에 액세스 할 수 있었던 CLR 기능을 활용합니다.
Marc L.

더 우아한? 이 예제는 그러한 간단한 문제에 비해 너무 커서 코드가 너무 끔찍해 보이기 때문에 볼 가치가 없습니다. 실제 프로젝트에서이 코드를 다른 사람의 문제로 만들지 마십시오.
KthProg

전체 IsAnyOf방법을 간단하게 다시 작성할 수 있습니다p_comparisons.Contains(p_parameter)
maksymiuk

7

이 답변이 표면에 닿는 것처럼 느껴지므로 조금 더 깊이 파고 들었습니다.

그래서 우리가 정말로하고 싶은 것은 컴파일되지 않는 것입니다.

// Won't compile... damn
public static void Main()
{
    try
    {
        throw new ArgumentOutOfRangeException();
    }
    catch (ArgumentOutOfRangeException)
    catch (IndexOutOfRangeException) 
    {
        // ... handle
    }

우리가 원하는 이유는 예외 처리기가 프로세스에서 나중에 필요한 것을 포착하지 않기 때문입니다. 물론, 우리는 예외를 잡아서해야 할 일을 '만약'으로 확인할 수 있지만, 솔직히 말하면 실제로는 원하지 않습니다. (FxCop, 디버거 문제, 못생긴)

그렇다면 왜이 코드가 컴파일되지 않습니까? 어떻게 그렇게 해킹 할 수 있습니까?

코드를 살펴보면 실제로 전화를 걸어야합니다. 그러나 MS Partition II에 따르면 IL 예외 처리기 블록은 이와 같이 작동하지 않습니다.이 경우 '예외'개체가 다른 유형을 가질 수 있기 때문에 의미가 있습니다.

또는 코드로 작성하려면 컴파일러에게 다음과 같은 작업을 수행하도록 요청하십시오 (전적으로 정확하지는 않지만 가장 가까운 것으로 생각됩니다).

// Won't compile... damn
try
{
    throw new ArgumentOutOfRangeException();
}
catch (ArgumentOutOfRangeException e) {
    goto theOtherHandler;
}
catch (IndexOutOfRangeException e) {
theOtherHandler:
    Console.WriteLine("Handle!");
}

이것이 컴파일되지 않는 이유는 분명합니다. '$ exception'객체가 갖는 유형과 값 (여기서 'e'변수에 저장되어 있음)은 무엇입니까? 컴파일러가 이것을 처리하도록하는 방법은 두 예외의 공통 기본 유형이 '예외'라는 점에 유의하고 변수에 두 예외를 모두 포함시킨 다음 포착 된 두 예외 만 처리하는 것입니다. 이것이 IL에서 구현되는 방식은 VB.Net에서 사용할 수있는 '필터'입니다.

C #에서 작동하려면 올바른 '예외'기본 유형의 임시 변수가 필요합니다. 코드 흐름을 제어하기 위해 분기를 추가 할 수 있습니다. 간다 :

    Exception ex;
    try
    {
        throw new ArgumentException(); // for demo purposes; won't be caught.
        goto noCatch;
    }
    catch (ArgumentOutOfRangeException e) {
        ex = e;
    }
    catch (IndexOutOfRangeException e) {
        ex = e;
    }

    Console.WriteLine("Handle the exception 'ex' here :-)");
    // throw ex ?

noCatch:
    Console.WriteLine("We're done with the exception handling.");

이것에 대한 명백한 단점은 우리가 올바르게 다시 던질 수 없다는 것입니다. 솔직히 말해서 그것이 그것이 못생긴 해결책이라는 것입니다. 분기 제거를 수행하여 추악함을 약간 수정하여 솔루션을 약간 향상시킬 수 있습니다.

Exception ex = null;
try
{
    throw new ArgumentException();
}
catch (ArgumentOutOfRangeException e)
{
    ex = e;
}
catch (IndexOutOfRangeException e)
{
    ex = e;
}
if (ex != null)
{
    Console.WriteLine("Handle the exception here :-)");
}

그것은 단지 '다시 던지기'만 남습니다. 이것이 작동하려면 'catch'블록 내부에서 처리를 수행 할 수 있어야합니다.이 작업을 수행하는 유일한 방법은 'Exception'개체를 잡는 것입니다.

이 시점에서 우리는 과부하 해결을 사용하여 다른 유형의 예외를 처리하거나 예외를 처리하는 별도의 함수를 추가 할 수 있습니다. 둘 다 단점이 있습니다. 시작하려면 다음은 도우미 기능으로 수행하는 방법입니다.

private static bool Handle(Exception e)
{
    Console.WriteLine("Handle the exception here :-)");
    return true; // false will re-throw;
}

public static void Main()
{
    try
    {
        throw new OutOfMemoryException();
    }
    catch (ArgumentException e)
    {
        if (!Handle(e)) { throw; }
    }
    catch (IndexOutOfRangeException e)
    {
        if (!Handle(e)) { throw; }
    }

    Console.WriteLine("We're done with the exception handling.");

그리고 다른 해결책은 Exception 객체를 잡아서 적절히 처리하는 것입니다. 위의 맥락에 따라 가장 문자 그대로의 번역은 다음과 같습니다.

try
{
    throw new ArgumentException();
}
catch (Exception e)
{
    Exception ex = (Exception)(e as ArgumentException) ?? (e as IndexOutOfRangeException);
    if (ex != null)
    {
        Console.WriteLine("Handle the exception here :-)");
        // throw ?
    }
    else 
    {
        throw;
    }
}

결론 :

  • 다시 던지기를 원하지 않으면 올바른 예외를 잡아 임시로 저장하는 것을 고려할 수 있습니다.
  • 핸들러가 단순하고 코드를 재사용하려는 경우 가장 좋은 해결책은 아마도 도우미 함수를 도입하는 것입니다.
  • 다시 던지기를 원한다면 FxCop과 디버거의 포착되지 않은 예외를 중단시키는 '예외'캐치 핸들러에 코드를 넣는 것 외에는 선택의 여지가 없습니다.

7

이것은 모든 C # 개발자가 결국 직면하는 고전적인 문제입니다.

질문을 두 가지 질문으로 나누겠습니다. 첫번째,

한 번에 여러 예외를 잡을 수 있습니까?

요컨대

다음 질문으로 이어지는

동일한 catch () 블록에서 여러 예외 유형을 잡을 수 없으므로 중복 코드를 작성하지 않으려면 어떻게합니까?

폴백 값이 구성하기에 저렴한 특정 샘플을 고려할 때 다음 단계를 따르고 싶습니다.

  1. 대체 값으로 WebId를 초기화하십시오.
  2. 임시 변수에 새 Guid를 구성하십시오.
  3. WebId를 완전히 구성된 임시 변수로 설정하십시오. 이것을 try {} 블록의 최종 문장으로 만드십시오.

따라서 코드는 다음과 같습니다.

try
{
    WebId = Guid.Empty;
    Guid newGuid = new Guid(queryString["web"]);
    // More initialization code goes here like 
    // newGuid.x = y;
    WebId = newGuid;
}
catch (FormatException) {}
catch (OverflowException) {}

예외가 발생하면 WebId는 반으로 구성된 값으로 설정되지 않으며 Guid.Empty로 유지됩니다.

폴백 값을 구성하는 것이 비싸고 값을 재설정하는 것이 훨씬 저렴하다면 재설정 코드를 자체 기능으로 옮길 것입니다.

try
{
    WebId = new Guid(queryString["web"]);
    // More initialization code goes here.
}
catch (FormatException) {
    Reset(WebId);
}
catch (OverflowException) {
    Reset(WebId);
}

이것은 훌륭한 "생태 코딩"입니다. 즉, 코드 및 데이터 풋 프린트에 대해 미리 생각하고 절반의 처리 된 값이 유출되지 않도록합니다. Jeffrey에게 감사합니다.
Tahir Khalid

6

모든 예외 스위치 내에서 많은 코드를 반복하고 있습니까? 방법을 추출하는 것과 같은 소리는 신의 생각일까요?

따라서 코드는 다음과 같습니다.

MyClass instance;
try { instance = ... }
catch(Exception1 e) { Reset(instance); }
catch(Exception2 e) { Reset(instance); }
catch(Exception) { throw; }

void Reset(MyClass instance) { /* reset the state of the instance */ }

아무도 왜 그 코드 중복을 알아 차리지 못했는지 궁금합니다.

C # 6부터는 이미 다른 사람들이 언급 한 예외 필터 가 있습니다. 따라서 위 코드를 다음과 같이 수정할 수 있습니다.

try { ... }
catch(Exception e) when(e is Exception1 || e is Exception2)
{ 
    Reset(instance); 
}

3
"아무도 왜 그 코드 중복을 알아 차리지 못했는지 궁금합니다." -어? 문제의 핵심은 코드 중복을 제거하는 것입니다.
Mark Amery

4

이 긴 스레드에 짧은 대답을 추가하고 싶었습니다. 언급되지 않은 것은 catch 문의 우선 순위이며,보다 구체적으로 파악하려는 각 예외 유형의 범위를 알고 있어야합니다.

예를 들어 "catch-all"예외를 Exception 으로 사용하면 다른 모든 catch 문보다 우선하고 분명히 컴파일러 오류가 발생하지만 순서를 반대로 바꾸면 catch 문을 연결할 수 있습니다. ) catch-all Exception 유형을 맨 아래에 놓을 수 있으며 try..catch 블록에서 상위를 수용하지 않은 예외를 캡처합니다.

            try
            {
                // do some work here
            }
            catch (WebException ex)
            {
                // catch a web excpetion
            }
            catch (ArgumentException ex)
            {
                // do some stuff
            }
            catch (Exception ex)
            {
                // you should really surface your errors but this is for example only
                throw new Exception("An error occurred: " + ex.Message);
            }

이 MSDN 문서를 검토하는 것이 좋습니다.

예외 계층


4

catch 절에없는 코드의 다른 부분에서와 같이 메소드에 공통 코드를 넣는 것과 같이 코드를 단순하게 유지하려고 시도하십시오.

예 :

try
{
    // ...
}
catch (FormatException)
{
    DoSomething();
}
catch (OverflowException)
{
    DoSomething();
}

// ...

private void DoSomething()
{
    // ...
}

내가 어떻게 할 것인가, 단순함 을 찾으려고 노력하는 것은 아름다운 패턴입니다.


3

한 가지 방법을 찾았지만 Daily WTF의 자료와 비슷합니다 .

catch (Exception ex)
{
    switch (ex.GetType().Name)
    {
        case "System.FormatException":
        case "System.OverflowException":
            WebId = Guid.Empty;
            break;
        default:
            throw;
    }
}

9
-1 투표, +5 WTF :-) 답변으로 표시되어서는 안되지만 재미 있습니다.
Aaron

1
우리가 얼마나 간단하게 할 수 있는지는 중요하지 않습니다. 그러나 그는 공전하지 않고 그것을 해결하기 위해 자신의 관점을 생각해 냈습니다. 정말 감사합니다.
Maxymus

2
실제로이 작업을 수행하지 말고 C # 6의 예외 필터 또는 다른 답변을 사용하십시오. 여기에 "이것은 한 가지 방법이지만 나쁜 일이며 더 나은 일을하고 싶습니다"라고 구체적으로 설명합니다.
Michael Stum

왜이게 나쁜가요? switch 문에서 직접 예외를 사용할 수 없다는 것에 당황했습니다.
MKesper

3
@ MKesper 나쁜 이유가 몇 가지 있습니다. 정규화 된 클래스 이름을 문자열 리터럴로 작성해야하므로 컴파일러에서 저장할 수없는 오타에 취약합니다. (이는 많은 상점에서 오류 사례가 잘 테스트되지 않아서 사소한 실수를 놓칠 가능성이 높기 때문에 중요합니다.) 또한 지정된 사례 중 하나의 하위 클래스 인 예외와 일치하지 않습니다 . 또한 문자열이기 때문에 VS의 "모든 참조 찾기"와 같은 도구를 사용하여 사례를 놓칠 수 있습니다. 특정 예외가 발견 될 때마다 정리 단계를 추가하려는 경우에 적합합니다.
Mark Amery

2

여기서 언급 할 가치가 있습니다. 여러 조합 (예외 오류 및 예외. 메시지)에 응답 할 수 있습니다.

TextBox, TextBlock 또는 CheckBox와 같은 내용으로 데이터 그리드에서 컨트롤 개체를 캐스팅하려고 할 때 유스 케이스 시나리오가 발생했습니다. 이 경우 반환 된 Exception은 동일하지만 메시지가 다양합니다.

try
{
 //do something
}
catch (Exception ex) when (ex.Message.Equals("the_error_message1_here"))
{
//do whatever you like
} 
catch (Exception ex) when (ex.Message.Equals("the_error_message2_here"))
{
//do whatever you like
} 

0

가장 짧은 답변을 제안하고 싶습니다 (하나 이상의 기능적 스타일 ).

        Catch<FormatException, OverflowException>(() =>
            {
                WebId = new Guid(queryString["web"]);
            },
            exception =>
            {
                WebId = Guid.Empty;
            });

이를 위해 System.Action과 유사한 몇 가지 "캐치"메서드 오버로드를 만들어야합니다.

    [DebuggerNonUserCode]
    public static void Catch<TException1, TException2>(Action tryBlock,
        Action<Exception> catchBlock)
    {
        CatchMany(tryBlock, catchBlock, typeof(TException1), typeof(TException2));
    }

    [DebuggerNonUserCode]
    public static void Catch<TException1, TException2, TException3>(Action tryBlock,
        Action<Exception> catchBlock)
    {
        CatchMany(tryBlock, catchBlock, typeof(TException1), typeof(TException2), typeof(TException3));
    }

당신이 원하는대로 등등. 그러나 한 번만 수행하면 모든 프로젝트에서 사용할 수 있습니다 (또는 너겟 패키지를 만들면 사용할 수도 있습니다).

그리고 CatchMany 구현 :

    [DebuggerNonUserCode]
    public static void CatchMany(Action tryBlock, Action<Exception> catchBlock,
        params Type[] exceptionTypes)
    {
        try
        {
            tryBlock();
        }
        catch (Exception exception)
        {
            if (exceptionTypes.Contains(exception.GetType())) catchBlock(exception);
            else throw;
        }
    }

추신 : 코드 단순성을 위해 null 검사를하지 않았으므로 매개 변수 유효성 검사를 추가하십시오.

ps2 catch에서 값을 반환하려면 동일한 Catch 메서드를 수행해야하지만 매개 변수의 Action 대신 return 및 Func를 사용해야합니다.


-15

시도하고 두 번 잡으십시오.

try
{
    WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
    WebId = Guid.Empty;
}
try
{
    WebId = new Guid(queryString["web"]);
}
catch (OverflowException)
{
    WebId = Guid.Empty;
}

그 단순한 것입니다!


3
음 이것은 질문의 목적을 무너 뜨리고 있습니다. 그는이 질문에 중복 코드를 제거하도록 요청합니다. 이 답변은 더 많은 중복 코드를 추가합니다.
James Esh

-23

C # 6.0에서 예외 필터는 예외 처리를 개선 한 것입니다.

try
{
    DoSomeHttpRequest();
}
catch (System.Web.HttpException e)
{
    switch (e.GetHttpCode())
    {
        case 400:
            WriteLine("Bad Request");
        case 500:
            WriteLine("Internal Server Error");
        default:
            WriteLine("Generic Error");
    }
}

13
이 예제는 예외 필터 사용을 보여주지 않습니다.
user247702

이것은 C # 6.0에서 예외를 필터링하는 표준 방법입니다
Kashif

5
예외 필터가 정확히 무엇인지 다시 살펴보십시오. 예제에서 예외 필터를 사용하고 있지 않습니다. 이 답변 에는 1 년 전에 게시 된 적절한 예가 있습니다.
user247702

6
예외 필터링의 예는 다음과 같습니다.catch (HttpException e) when e.GetHttpCode() == 400 { WriteLine("Bad Request"; }
saluce
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.