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



Answers

편집 : 나는 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;
}
Question

단순히 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 . 그러나 예기치 않은 예외가있는 경우에도 여전히 예외를 던지고 싶습니다.




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

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

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

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

bool exThrown = false;

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

if (exThrown)
{
    // Something else
}



This is a classic problem every C# developer faces eventually.

Let me break your question into 2 questions. The first,

Can I catch multiple exceptions at once?

In short, no.

Which leads to the next question,

How do I avoid writing duplicate code given that I can't catch multiple exception types in the same catch() block?

Given your specific sample, where the fall-back value is cheap to construct, I like to follow these steps:

  1. Initialize WebId to the fall-back value.
  2. Construct a new Guid in a temporary variable.
  3. Set WebId to the fully constructed temporary variable. Make this the final statement of the try{} block.

So the code looks like:

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) {}

If any exception is thrown, then WebId is never set to the half-constructed value, and remains Guid.Empty.

If constructing the fall-back value is expensive, and resetting a value is much cheaper, then I would move the reset code into its own function:

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



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

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

나는 원래 다른 목적을 위해 작성된 내 라이브러리에서이 확장을 이미 가지고 있지만 예외에 대한 type 검사에 완벽하게 작동했습니다. 게다가, imho, 그것들은 여러 덩어리보다 깔끔해 보입니다. 진술. 또한, 허용 된 대답과는 달리, 나는 명시 적 예외 처리를 선호하므로 ex is ... 은 부모 클래스에 배정 가능한 클래스가 할당 될 때 원치 않는 행동을합니다.)

용법

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

Two Sample NUnit Unit Tests

Matching behaviour for Exception types is exact (ie. A child IS NOT a match for any of its parent types).

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*/
            }
        }
    }
}



Just call the try and catch twice.

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

It is just that Simple!!




Wanted to added my short answer to this already long thread. Something that hasn't been mentioned is the order of precedence of the catch statements, more specifically you need to be aware of the scope of each type of exception you are trying to catch.

For example if you use a "catch-all" exception as Exception it will preceed all other catch statements and you will obviously get compiler errors however if you reverse the order you can chain up your catch statements (bit of an anti-pattern I think) you can put the catch-all Exception type at the bottom and this will be capture any exceptions that didn't cater for higher up in your try..catch block:

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

I highly recommend folks review this MSDN document:

Exception Hierarchy




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
}



So you´re repeating lots of code within every exception-switch? Sounds like extracting a method would be god idea, doesn´t it?

So your code comes down to this:

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 */ }

I wonder why no-one noticed that code-duplication.

From C#6 you furthermore have the exception-filters as already mentioned by others. So you can modify the code above to this:

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



응용 프로그램을 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 참조하십시오. 그리고 그 차이를 보여주는 예 .




@Mheheal

약간 수정 된 코드 버전 :

catch (Exception ex)
{
   Type exType = ex.GetType();
   if (exType == typeof(System.FormatException) || 
       exType == typeof(System.OverflowException)
   {
       WebId = Guid.Empty;
   } else {
      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();
}



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

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

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

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

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

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




Related