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




throw new exception c# (21)

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

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

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

나는 두 가지 예외를 모두 잡아서 WebId = Guid.Empty 호출 만 호출하는 방법이 있을까요?

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


다른 사람들이 지적했듯이 catch 블록 내에 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 문을 사용하는 것과는 다르며 예외 필터 사용하면 스택을 unwind 하지 않습니다 .

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

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

Microsoft.Net.Compilers 설치 패키지

이 글을 쓰는 시점에서 C # 6에 대한 지원이 포함됩니다.

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


catch 범위 내에서 if 문을 사용하지 않으려면 C# 6.0 에서 이미 미리보기 버전의 CLR에서 지원되었지만 VB.NET / MSIL 에서만 존재 하는 Exception Filters 구문사용할 수 있습니다 .

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

이 코드는 InvalidDataException 또는 ArgumentNullException 경우에만 Exception 을 catch합니다.

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

static int a = 8;

...

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

catch 의 범위 안에있는 if 문과는 달리 Exception FiltersExceptions throw 할 수 없으며 Exceptions 발생하거나 조건이 true 가 아닌 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 가있는 경우 첫 번째 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 로 변환되며 ExceptionsFilter 1Filter 2 로 표시된 영역 내에서 Exceptions 수 없지만 Exceptions 를 throw하는 필터는 대신 실패합니다. 마지막으로 endfilter 명령이 필터의 성공 / 실패를 결정하기 전에 스택에 푸시 된 비교 값 ( Catch 1 XOR Catch 2 가 그에 따라 실행 됨)

또한 Guid 에는 특히 Guid.TryParse 메서드가 있습니다.


그래서 모든 예외 스위치 내에서 많은 코드를 반복하고 있습니까? 방법 추출과 같은 소리는 신 아이디어 일 텐데, 그렇지 않니?

따라서 코드가 아래와 같습니다.

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부터는 이미 다른 사람들이 언급 한 exception-filters 가 있습니다. 그래서 위의 코드를 다음과 같이 수정할 수 있습니다 :

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

C # 6.0에서 Exception Filters는 예외 처리 개선입니다.

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

Joseph Daigle의 대답 은 좋은 해결책이지만 다음 구조가 조금 더 깔끔하고 오류가 발생하지 않는 것으로 나타났습니다.

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

    // Handle exception
}

표현식을 뒤집는 데는 몇 가지 장점이 있습니다.

  • return 문은 필요하지 않습니다.
  • 코드가 중첩되어 있지 않습니다.
  • 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
}

System.Exception catch하고 유형을 켜십시오.

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

    throw;
}

어때?

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

이 답변이 표면에 닿았을 때부터 조금 더 깊이 파고 들려고했습니다.

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

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

우리가 이것을 원하게되는 이유는 예외 처리기가 나중에 우리가 필요로하는 것들을 잡는 것을 원하지 않기 때문입니다. 물론, 우리는 Exception을 잡아서 'if'로 무엇을 할지를 검사 할 수 있습니다. 그러나 솔직히 말하면, 우리는 그것을 정말로 원하지 않습니다. (FxCop, 디버거 문제, 추한)

그렇다면 왜이 코드를 컴파일하지 않을 것인가? 그런 식으로 어떻게 해킹 할 수 있을까?

코드를 보면 우리가 정말로하고 싶은 것은 콜을 전달하는 것입니다. 그러나 MS 파티션 II에 따르면 일리노이 예외 핸들러 블록은 이와 같이 작동하지 않습니다.이 경우 '예외'개체가 다른 유형을 가질 수 있으므로이 경우 이해가됩니다.

또는 코드로 작성하기 위해 컴파일러에게 이와 같은 작업을 수행하도록 요청합니다 (완전히 정확하지는 않지만 아마도 가장 가까운 것입니다).

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

이것이 컴파일되지 않는 이유는 '$ 예외'객체가 가지고있는 유형과 값 (여기에 변수 'e'에 저장되어있는 값)은 무엇인가? 우리가 컴파일러에서 처리하도록하는 방법은 두 예외의 공통 기본 유형이 'Exception'이며, 두 예외를 모두 포함하는 변수를 사용하고 catch 된 두 예외 만 처리하는 것입니다. 이것이 일리노이에서 구현되는 방식은 '필터'로서 VB.Net에서 사용할 수 있습니다.

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

    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과 디버거의 잡히지 않은 예외를 벗어나는 'Exception'catch 핸들러에 코드를 넣을 수 밖에 없습니다.

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

링크에 포함 된 내용은 직접 질문에 답변하지 않지만 다음과 같이 확장하는 것은 쉽지 않습니다.

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

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

(기본적으로 자신을 반환하는 또 다른 빈 Catch 오버로드를 제공하십시오)

이것에 대한 더 큰 의문이있는 이유 입니다. 나는 비용이 여기에 이득보다 중요하다고 생각하지 않습니다 :)


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

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

TryParse 는 예외를 throw하지 않으며 형식이 잘못되면 false를 반환하고 WebId를 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에서 out-parameters의 해체가 구현 될 때 쓸데없는 대답을 쓸모없는 다음 업데이트가 나온다. :)


CodeAnalysis / FxCop 이 일반적인 예외 유형을 포착한다는 사실에 대해 불만을 표시한다는 것을 제외하면 허용되는 대답이 받아 들여질 수 있습니다.

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

CA1800 : "as 연산자"대신에 "as"연산자의 결과를 테스트하는 것을 고려해야한다는 불필요한 말은 하지 말고 , 그렇게하면 각 예외를 개별적으로 catch하는 것보다 더 많은 코드를 작성하게됩니다.

어쨌든, 내가 할 일은 다음과 같습니다.

bool exThrown = false;

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

if (exThrown)
{
    // Something else
}

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

귀하의 질문을 두 가지 질문으로 나누십시오. 첫번째,

한 번에 여러 개의 예외를 catch 할 수 있습니까?

간단히 말해서, 아니오.

어느 것이 다음 질문으로 이어집니다.

동일한 catch () 블록에서 여러 예외 유형을 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);
}

어쩌면 catch 절 안에 있지 않은 코드의 다른 부분 에서처럼 일반 코드를 메서드에 넣는 등의 간단한 코드를 유지하려고 시도 할 수 있습니까?

예 :

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

// ...

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

내가 어떻게 할 것인가, 심플함 을 찾는 것은 아름다운 패턴이다.


이미 긴 스레드에 짧은 대답을 추가하고 싶었습니다. 언급되지 않은 무언가는 catch 문의 우선 순위입니다. 특히 잡으려고하는 각 예외 유형의 범위를 알고 있어야합니다.

예를 들어 Exception 으로 "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 문서를 검토하는 것이 좋습니다.

예외 계층 구조


이것은 매트의 대답의 변형입니다 (나는 이것이 조금 더 깨끗하다고 ​​느낍니다) ... 방법을 사용하십시오 :

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
}

업데이트 2015-12-15 : C # 6의 경우 https://.com/a/22864936/1718702 를 참조 https://.com/a/22864936/1718702 . 그것은 언어에서 현재 표준적이고 깨끗합니다.

한 번 붙잡고 예외를 필터링 하는 보다 세련된 솔루션 을 원하는 사람들을 대상 으로 아래에 설명 된 확장 방법을 사용합니다.

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

용법

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

IsAnyOf.cs 확장 (Dependancies에 대한 전체 오류 처리 예제 참조)

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(Exception)))
                    throw;

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

응용 프로그램을 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의 유일한 새로운 기능입니다. 첫째, 재 드로잉은 캐치를 건너 뛰는 것보다 더 많은 오버 헤드를 의미합니다. 둘째로, 의미 상 동일하지 않습니다. 새로운 기능은 코드를 디버깅 할 때 스택을 그대로 유지합니다. 이 기능이 없으면 크래시 덤프가 덜 유용하거나 쓸모가 없습니다.

roslyn.codeplex.com/discussions/541301 에서 이에 roslyn.codeplex.com/discussions/541301 참조하십시오. 그리고 그 차이를 보여주는 예 .


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

C # 7을 사용하면 switch 문을 쉽게 읽을 수 있도록 유지하면서 Michael Stum의 대답을 향상시킬 수 있습니다.

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

편집 : 나는 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; 의 3 개의 동일한 인스턴스를 /* write to a log, whatever... */ return; 첫 번째 예에서

하지만 그게 내 요점이야. 모두 기능 / 방법에 대해 들어 봤어? 진지하게. 일반적인 ErrorHandler 함수를 작성하고, 각 catch 블록에서 호출하십시오.

여러분이 나에게 묻는다면, 두 번째 예제 ( ifis 키워드 사용)는 프로젝트의 유지 관리 단계에서 읽기 쉽지 않으며 동시에 오류 발생 가능성이 훨씬 큽니다.

상대적으로 프로그래밍에 익숙하지 않은 사용자를위한 유지 관리 단계는 프로젝트의 전체 수명 기간 중 98.7 % 이상을 차지하게 될 것이며 유지 관리를 수행하는 불량배는 거의 확실하게 다른 사람이 될 것입니다. 그리고 당신의 이름을 저주하는 직업에 그들의 시간의 50 %를 쓸 아주 좋은 기회가 있습니다.

그리고 당연히 FxCop이 당신을 짖으며 실행중인 프로그램과 정확하게 관련이있는 코드에 속성을 추가해야합니다. FxCop이 99.9 %의 사례에서 완전히 그렇다는 것을 무시하도록 FxCop에 지시합니다. 신고하는 것이 맞다. 그리고, 죄송 합니다만, 나는 틀릴 수도 있습니다. 그러나 "ignore"속성이 실제로 앱에 컴파일되는 것을 끝내지는 않습니까?

하나의 라인에 전체 if 테스트를두면 더 읽기 쉬울 것입니까? 나는 그렇게 생각하지 않는다. 나는 한 프로그래머가 한 줄에 더 많은 코드를 넣으면 실행 속도가 빨라질 것이라고 오래 전에 주장했다. 그러나 물론 그는 엄청난 열매를 맺고 있었다. 인터프리터 나 컴파일러가 긴 라인을 개별 라인 당 하나의 명령문으로 분리하는 방법에 대해서는 (직설적 인 얼굴로 - 도전적이었던) 설명했다. 컴파일러를 외면하려고 시도하는 대신 코드를 읽을 수있게 만들었습니다. 아무런 영향을 미치지 않았습니다. 그러나 나는 빗 나간다.

지금부터 1 ~ 2 개의 예외 유형을 추가 할 때 얼마나 쉽게 읽을 수 있습니까? (답 : 읽기 쉽지 않습니다.)

주요 요점 중 하나는 실제로 우리가 매일보고있는 텍스트 소스 코드를 포맷하는 대부분의 요점은 코드가 실행될 때 실제로 일어나고있는 일을 다른 사람에게 분명히 알리는 것입니다. 왜냐하면 컴파일러는 소스 코드를 완전히 다른 것으로 바꾸어 코드 서식 스타일을 신경 쓰지 않기 때문입니다. 그래서 all-on-one-line은 완전히 짜증납니다.

그냥 ...

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

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




exception-handling