질문자 :Michael Stum
System.Exception
잡는 것은 권장하지 않습니다. 대신 "알려진" 예외만 잡아야 합니다.
이제 이것은 때때로 불필요한 반복적인 코드로 이어집니다. 예를 들면 다음과 같습니다.
try { WebId = new Guid(queryString["web"]); } catch (FormatException) { WebId = Guid.Empty; } catch (OverflowException) { WebId = Guid.Empty; }
궁금합니다. 두 예외를 모두 잡아내고 WebId = Guid.Empty
호출을 한 번만 호출하는 방법이 있습니까?
GUID
뿐이므로 다소 간단합니다. 그러나 개체를 여러 번 수정하고 조작 중 하나가 예상대로 실패하면 object
를 "재설정"하려고 하는 코드를 상상해 보십시오. 그러나 예기치 않은 예외가 발생하면 여전히 더 높게 던지고 싶습니다.
System.Exception
하고 유형을 켜십시오.
catch (Exception ex) { if (ex is FormatException || ex is OverflowException) { WebId = Guid.Empty; return; } throw; }
Joseph Daigle편집: 나는 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의 세 가지 동일한 인스턴스를 /* write to a log, whatever... */ return;
첫 번째 예에서.
그러나 그것은 일종의 내 요점입니다. 함수/메소드에 대해 들어보셨죠? 진지하게. ErrorHandler
함수를 작성하고 각 catch 블록에서 호출합니다.
저에게 묻는다면 두 번째 예( if
및 is
키워드 포함)는 가독성이 현저히 낮고 동시에 프로젝트의 유지 관리 단계에서 오류가 발생하기 쉽습니다.
비교적 프로그래밍에 익숙하지 않은 사람을 위한 유지 관리 단계는 프로젝트 전체 수명의 98.7% 이상을 구성할 것이며 유지 관리를 하는 가난한 schmuck은 거의 확실히 당신이 아닌 다른 사람이 될 것입니다. 그리고 그들이 당신의 이름을 저주하는 일에 50%의 시간을 보낼 가능성이 매우 높습니다.
그리고 물론의 FxCop 당신의 당신이 또한 정확하게 실행중인 프로그램을 할 압축 한 코드에 속성을 추가 할 수 있고, 경우 99.9 %에 완전히이라고 문제를 무시의 FxCop 말씀 만이 그렇게 껍질 신고에 맞습니다. 죄송합니다. 제가 잘못 알고 있을 수도 있지만 "무시" 속성이 실제로 앱에 컴파일되지 않습니까?
전체 if
테스트를 한 줄에 넣으면 가독성이 더 높아지나요? 나는 그렇게 생각하지 않는다. 내 말은, 나는 오래전에 한 줄에 더 많은 코드를 넣으면 "더 빠르게 실행"될 것이라고 격렬하게 주장하는 다른 프로그래머가 있었습니다. 그러나 물론 그는 미친 듯이 열광했습니다. 인터프리터나 컴파일러가 어떻게 긴 줄을 줄당 하나의 명령어로 분리할 수 있는지 설명하려고 노력했습니다. 컴파일러를 능가하려고 하는 대신 코드를 읽을 수 있게 만들었습니다. 그러나 나는 빗나간다.
당신은 지금부터 세 이상의 예외 유형, 한 달 또는 두를 추가 할 때 얼마나 덜 읽을 수있는이 GET합니까? (답: 가독성이 훨씬 떨어집니다.)
실제로 주요 요점 중 하나는 우리 모두가 매일 보고 있는 텍스트 소스 코드의 형식을 지정하는 대부분의 요점은 코드가 실행될 때 실제로 일어나는 일을 다른 사람들에게 정말, 정말 분명하게 만드는 것입니다. 컴파일러가 소스 코드를 완전히 다른 것으로 바꾸고 코드 서식 스타일에 대해 신경을 덜 쓰기 때문입니다. 그래서 올온원 라인도 완전히 엉망입니다.
말만...
// super sucks... catch( Exception ex ) { if ( ex is FormatException || ex is OverflowException || ex is ArgumentNullException ) { // write to a log, whatever... return; } throw; }
Craig Tullis다른 사람들이 지적했듯이 if
문을 사용하여 무슨 일이 일어나고 있는지 확인할 수 있습니다. 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) { … }
catch
블록 내에서 if
문을 사용하는 것과 다르며 예외 필터를 사용하면 스택이 해제 되지 않습니다.
Visual Studio 2015 를 다운로드하여 이를 확인할 수 있습니다.
Visual Studio 2013을 계속 사용하려면 다음 nuget 패키지를 설치할 수 있습니다.
설치 패키지 Microsoft.Net.Compilers
작성 시점에는 C# 6에 대한 지원이 포함됩니다.
이 패키지를 참조하면 시스템 설치 버전이 아닌 패키지에 포함된 특정 버전의 C# 및 Visual Basic 컴파일러를 사용하여 프로젝트가 빌드됩니다.
Joe불행히도 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(); }
Greg Beech이제 예외 필터를 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..? }
Mat J완전성을 위해 .NET 4.0 부터 코드는 다음과 같이 다시 작성할 수 있습니다.
Guid.TryParse(queryString["web"], out WebId);
TryParse 는 예외를 발생시키지 않으며 형식이 잘못된 경우 false를 반환하고 WebId를 Guid.Empty
설정합니다.
C# 7 부터 별도의 줄에 변수를 도입하는 것을 피할 수 있습니다.
Guid.TryParse(queryString["web"], out Guid webId);
또한 버전 4.6에서 아직 .NET Framework에서 사용할 수 없는 반환 튜플을 구문 분석하기 위한 메서드를 만들 수도 있습니다.
(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;
이 쓸모없는 대답에 대한 다음 쓸모없는 업데이트는 out-parameters의 해체가 C# 12에서 구현될 때 옵니다. :)
Athari애플리케이션을 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의 유일한 새로운 기능입니다. 첫째, re-throw는 catch를 건너뛰는 것보다 더 많은 오버헤드를 의미합니다. 둘째, 의미상 동일하지 않습니다. 새로운 기능은 코드를 디버깅할 때 스택을 그대로 유지합니다. 이 기능이 없으면 크래시 덤프는 덜 유용하거나 심지어 쓸모가 없습니다.
CodePlex에서 이에 대한 토론을 참조하십시오. 더 이상 사용할 수 없습니다. 그리고 차이점을 보여주는 예 입니다.
Maniero당신이를 사용하지 않을 경우 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; }
이 코드는 InvalidDataException
또는 ArgumentNullException
경우에만 Exception
를 포착합니다.
when
절 안에 기본적으로 어떤 조건도 넣을 수 있습니다.
static int a = 8; ... catch (Exception exception) when (exception is InvalidDataException && a == 8) { Console.WriteLine("Catch"); }
catch
의 범위 내의 if
문과 달리 Exception Filters
Exceptions
throw할 수 없으며, 예외가 발생하거나 조건이 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](https://i.stack.imgur.com/uEfE9.jpg)
또한 특히 Guid
에는 Guid.TryParse
메서드가 있습니다.
Tamir VeredC# 7을 사용하면 switch 문의 가독성을 유지하면서 Michael Stum의 답변을 개선할 수 있습니다.
catch (Exception ex) { switch (ex) { case FormatException _: case OverflowException _: WebId = Guid.Empty; break; default: throw; } }
Orace 주석 덕분에 C# 8에서는 폐기 변수를 생략하여 이를 단순화할 수 있습니다.
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 }; }
Nechemia Hoffmann이 지적했듯이. 후자의 예에서는 스택 추적이 손실됩니다.
FabianC# 9용 업데이트
C# 9에서 향상된 새로운 패턴 일치 기능을 사용하여 예외 필터의 식을 줄일 수 있습니다. 이제 여러 예외를 잡는 것은 간단합니다.
try { WebId = new Guid(queryString["web"]); } catch (Exception e) when (e is FormatException or OverflowException) { WebId = Guid.Empty; }
Nechemia HoffmannCodeAnalysis/FxCop 이 일반적인 예외 유형을 포착한다는 사실에 대해 불평한다는 점을 제외하고 허용되는 답변은 허용되는 것 같습니다.
또한 "is" 연산자는 성능을 약간 저하시킬 수 있습니다.
CA1800: "대신 'as' 연산자의 결과를 테스트하는 것을 고려하십시오"라고 불필요하게 캐스트하지 마십시오 . 하지만 그렇게 하면 각 예외를 개별적으로 catch하는 것보다 더 많은 코드를 작성하게 됩니다.
여하튼 내가 할 일은 다음과 같습니다.
bool exThrown = false; try { // Something } catch (FormatException) { exThrown = true; } catch (OverflowException) { exThrown = true; } if (exThrown) { // Something else }
MattC# 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"); }
SHM이것은 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 }
bsaraJoseph Daigle의 답변 은 좋은 솔루션이지만 다음 구조가 약간 더 깔끔하고 오류가 덜 발생한다는 것을 알았습니다.
catch(Exception ex) { if (!(ex is SomeException || ex is OtherException)) throw; // Handle exception }
식을 반전하면 다음과 같은 몇 가지 이점이 있습니다.
- 반품 명세서는 필요하지 않습니다
- 코드가 중첩되지 않음
- Joseph의 솔루션에서 표현과 분리된 'throw' 또는 'return' 문장을 잊어버릴 위험이 없습니다.
한 줄로 압축할 수도 있습니다(아주 예쁘지는 않지만).
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 }
Stefan T@마이클
코드의 약간 수정된 버전:
catch (Exception ex) { Type exType = ex.GetType(); if (exType == typeof(System.FormatException) || exType == typeof(System.OverflowException) { WebId = Guid.Empty; } else { throw; } }
문자열 비교는 추하고 느립니다.
FlySwat어때요
try { WebId = Guid.Empty; WebId = new Guid(queryString["web"]); } catch (FormatException) { } catch (OverflowException) { }
Maurice 링크에 있는 내용은 귀하의 질문에 직접 답변하지 않지만 다음과 같이 확장하는 것은 간단합니다.
static void Main() { Action body = () => { ...your code... }; body.Catch<InvalidOperationException>() .Catch<BadCodeException>() .Catch<AnotherException>(ex => { ...handler... })(); }
(기본적으로 자체를 반환 Catch
이것 에 대한 더 큰 질문 은 왜 입니다 . 비용이 이득보다 크다고 생각하지 않습니다 :)
nawfalcatch (Exception ex) { if (!( ex is FormatException || ex is OverflowException)) { throw; } Console.WriteLine("Hello"); }
Konstantin Spirin업데이트 2015-12-15: C#6의 경우 https://stackoverflow.com/a/22864936/1718702를 참조하십시오. 그것은 더 깨끗하고 이제 언어의 표준입니다.
한 번만 포착하고 예외를 필터링하는 보다 우아한 솔루션 을 원하는 사람들을 위해 아래에 설명된 대로 확장 방법을 사용합니다.
원래 다른 목적으로 작성된 이 확장 기능이 내 라이브러리에 이미 있었지만 예외에 type
게다가 임호, 한 뭉치보다 더 깨끗해 보여 ||
진술. 또한 허용되는 답변과 달리 명시적 예외 처리를 선호하므로 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*/ } } } }
HodlDwon이러한 답변이 표면에 닿은 것 같아서 조금 더 깊이 파헤쳐 보았습니다.
따라서 우리가 정말로 하고 싶은 것은 컴파일되지 않는 것입니다.
// 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' 블록 내에서 처리를 수행할 수 있어야 합니다. 이 작업을 수행하는 유일한 방법은 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과 디버거의 잡히지 않은 예외를 깨는 'Exception' catch 핸들러에 코드를 넣는 것 외에는 선택의 여지가 없습니다.
atlaste이것은 모든 C# 개발자가 결국 직면하게 되는 고전적인 문제입니다.
귀하의 질문을 2개의 질문으로 나누겠습니다. 첫번째,
한 번에 여러 예외를 잡을 수 있습니까?
요컨대, 아닙니다.
다음 질문으로 이어지는,
동일한 catch() 블록에서 여러 예외 유형을 catch할 수 없는 경우 중복 코드 작성을 피하려면 어떻게 해야 합니까?
대체 값이 구성하기에 저렴한 특정 샘플을 고려할 때 다음 단계를 따르고 싶습니다.
- WebId를 폴백 값으로 초기화합니다.
- 임시 변수에 새 Guid를 구성합니다.
- 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 Rennie한 가지 방법을 찾았지만 이것은 Daily WTF의 자료와 비슷해 보입니다.
catch (Exception ex) { switch (ex.GetType().Name) { case "System.FormatException": case "System.OverflowException": WebId = Guid.Empty; break; default: throw; } }
Community Wiki그래서 모든 예외 스위치 내에서 많은 코드를 반복하고 있습니까? 방법을 추출하는 것은 신의 생각처럼 들리지 않습니까?
따라서 코드는 다음과 같습니다.
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); }
HimBromBeere이미 긴 스레드에 짧은 답변을 추가하고 싶었습니다. 언급되지 않은 것은 catch 문의 우선 순위입니다. 특히 catch하려는 각 예외 유형의 범위를 알고 있어야 합니다.
예를 들어 "catch-all" 예외를 예외로 사용 하면 다른 모든 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 문서를 검토할 것을 적극 권장합니다.
예외 계층
Trevorcatch 절 안에 있지 않은 코드의 다른 부분에서 하는 것처럼 메서드에 일반 코드를 넣는 것과 같이 코드를 단순하게 유지하려고 하시겠습니까?
예:
try { // ... } catch (FormatException) { DoSomething(); } catch (OverflowException) { DoSomething(); } // ... private void DoSomething() { // ... }
그냥 내가 하는 방법 심플함을 찾는 것이 아름다운 패턴
Żubrówka여기에서 언급할 가치가 있습니다. 여러 조합(Exception error 및 exception.message)에 응답할 수 있습니다.
TextBox, TextBlock 또는 CheckBox와 같은 콘텐츠를 사용하여 DataGrid에서 제어 개체를 캐스팅하려고 할 때 사용 사례 시나리오가 발생했습니다. 이 경우 반환된 예외는 동일했지만 메시지는 다양했습니다.
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 }
George나는 가장 짧은 대답을 제안하고 싶습니다 (하나 더 기능적인 스타일 ):
Catch<FormatException, OverflowException>(() => { WebId = new Guid(queryString["web"]); }, exception => { WebId = Guid.Empty; });
이를 위해 System.Action과 유사한 여러 "Catch" 메서드 오버로드를 만들어야 합니다.
[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; } }
ps 코드 단순성을 위해 null 검사를 넣지 않았으므로 매개 변수 유효성 검사를 추가하는 것이 좋습니다.
ps2 catch에서 값을 반환하려면 동일한 Catch 메서드를 수행해야 하지만 매개변수에서 Action 대신에 반환과 Func를 사용해야 합니다.
Evgeny GorbovoyC# 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"); } }
Kashif출처 : http:www.stackoverflow.com/questions/136035/catch-multiple-exceptions-at-once