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



13 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;
}
raise exception throw

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




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



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




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

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



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

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
}



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



어때?

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



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

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

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

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

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

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




업데이트 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*/
            }
        }
    }
}



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

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

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



내가 그것을하는 한 방법을 찾았지만, 이것은 The Daily WTF의 재료와 비슷해 보인다 .

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



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

예 :

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

// ...

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

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




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



Related