"catch, when"을 사용하여 예외 잡기


94

C #에서 특정 조건이 충족 될 때 catch 핸들러를 실행할 수있는이 새로운 기능을 발견했습니다.

int i = 0;
try
{
    throw new ArgumentNullException(nameof(i));
}
catch (ArgumentNullException e)
when (i == 1)
{
    Console.WriteLine("Caught Argument Null Exception");
}

나는 이것이 언제 유용 할 수 있는지 이해하려고 노력하고 있습니다.

하나의 시나리오는 다음과 같을 수 있습니다.

try
{
    DatabaseUpdate()
}
catch (SQLException e)
when (driver == "MySQL")
{
    //MySQL specific error handling and wrapping up the exception
}
catch (SQLException e)
when (driver == "Oracle")
{
    //Oracle specific error handling and wrapping up of exception
}
..

그러나 이것은 다시 동일한 핸들러 내에서 수행 할 수 있고 드라이버 유형에 따라 다른 메소드에 위임 할 수 있습니다. 코드를 더 쉽게 이해할 수 있습니까? 틀림없이 아니오.

제가 생각할 수있는 또 다른 시나리오는 다음과 같습니다.

try
{
    SomeOperation();
}
catch(SomeException e)
when (Condition == true)
{
    //some specific error handling that this layer can handle
}
catch (Exception e) //catchall
{
    throw;
}

다시 이것은 내가 할 수있는 일입니다.

try
{
    SomeOperation();
}
catch(SomeException e)
{
    if (condition == true)
    {
        //some specific error handling that this layer can handle
    }
    else
        throw;
}

'catch, when'기능을 사용하면 핸들러를 건너 뛰고 핸들러 내에서 특정 사용 사례를 처리 할 때보 다 훨씬 빨리 스택 해제가 발생할 수 있으므로 예외 처리가 더 빨라 집니까? 사람들이 모범 사례로 채택 할 수있는이 기능에 더 적합한 특정 사용 사례가 있습니까?


8
경우에 유용의 when요구는 예외 자체에 액세스 할 수
팀 Schmelter

1
그러나 그것은 우리가 핸들러 블록 자체에서도 할 수있는 일입니다. '조금 더 체계적인 코드'외에 다른 이점이 있습니까?
MS Srikkanth

3
그러나 원하지 않는 예외를 이미 처리했습니다. 다른 곳에서 잡으려면 어떻게해야 try..catch...catch..catch..finally합니까?
Tim Schmelter

4
@ user3493289 :이 인수 다음에 예외 처리기에서 자동 유형 검사가 필요하지 않습니다. 허용 catch (Exception ex), 유형 검사 등 만 할 수 있습니다 throw. 약간 더 체계적인 코드 (코드 노이즈 방지)가 정확히이 기능이 존재하는 이유입니다. (이것은 실제로 기능을 많이 마찬가지입니다.)
Heinzi

2
@TimSchmelter 감사합니다. 답변으로 게시하면 수락하겠습니다. '처리를위한 조건이 예외에 의존하는 경우'실제 시나리오는 다음이 될 것입니다 그래서,이 기능 / 사용
MS Srikkanth

답변:


118

캐치 블록을 사용하면 이미 예외 유형 을 필터링 할 수 있습니다 .

catch (SomeSpecificExceptionType e) {...}

when절을 사용하면이 필터를 일반 표현식으로 확장 할 수 있습니다.

따라서 예외 유형 이 여기에서 처리되어야하는지 여부를 결정할 수있을만큼 예외 유형 이 명확하지 않은 경우에이 절을 사용합니다 .when


일반적인 사용 사례는 실제로 여러 종류의 오류에 대한 래퍼 인 예외 유형입니다 .

다음은 내가 실제로 사용한 사례입니다 (VB에서 이미 꽤 오랫동안이 기능을 사용하고 있습니다).

try
{
    SomeLegacyComOperation();
}
catch (COMException e) when (e.ErrorCode == 0x1234)
{
    // Handle the *specific* error I was expecting. 
}

속성 SqlException도있는과 동일 ErrorCode합니다. 대안은 다음과 같습니다.

try
{
    SomeLegacyComOperation();
}
catch (COMException e)
{
    if (e.ErrorCode == 0x1234)
    {
        // Handle error
    }
    else
    {
        throw;
    }
}

틀림없이 덜 우아하고 스택 트레이스를 약간 깨뜨립니다 .

또한 동일한 try-catch-block에서 동일한 유형 의 예외를 두 번 언급 할 수 있습니다 .

try
{
    SomeLegacyComOperation();
}
catch (COMException e) when (e.ErrorCode == 0x1234)
{
    ...
}
catch (COMException e) when (e.ErrorCode == 0x5678)
{
    ...
}

when조건 없이는 불가능 합니다.


2
두 번째 접근 방식은 다른에서 잡는 것을 허용 catch하지 않습니까?
Tim Schmelter

@TimSchmelter. 진실. 동일한 블록에서 모든 COMException 을 처리해야합니다 .
Heinzi

(가) 동안 when수 있습니다 당신은 같은 예외가 여러 번 입력 처리합니다. 그것은 중요한 차이이기 때문에 또한 언급해야합니다. 없이는 when컴파일러 오류가 발생합니다.
Tim Schmelter

1
제가 아는 한, "요약 :"다음 부분이 답의 첫 번째 줄이어야합니다.
CompuChip

1
@ user3493289 : 그것은 종종 추악한 코드의 경우입니다. 당신은 "처음에 이런 혼란에 빠져서는 안된다"고 생각하고 "이 디자인을 우아하게 지원하고 언어를 재 설계 할 수있는 방법이있을 수있다"고 생각합니다. 특정 상황 덜 추한하게 뭔가 당신이 더 임계 :-) 내에서 수행 얻을 수 있도록이 경우 당신이 할 수있는 캐치 절 당신의 설정을 할 방법 추한에 대한 임계 값의 종류가있다
스티브 Jessop

37

Roslyn의 위키에서 (강조 내) :

예외 필터는 스택을 손상시키지 않기 때문에 잡아서 다시 던지는 것보다 선호됩니다 . 나중에 예외로 인해 스택이 덤프되는 경우 다시 던져진 마지막 위치가 아니라 원래 출처가 어디인지 확인할 수 있습니다.

또한 부작용에 예외 필터를 사용하는 것은 일반적이고 허용되는 "남용"형식입니다. 예 : 로깅. 코스를 가로 채지 않고 예외 "비행"을 검사 할 수 있습니다 . 이러한 경우 필터는 종종 부작용을 실행하는 거짓 반환 도우미 함수에 대한 호출이됩니다.

private static bool Log(Exception e) { /* log it */ ; return false; }

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

첫 번째 요점은 입증 할 가치가 있습니다.

static class Program
{
    static void Main(string[] args)
    {
        A(1);
    }

    private static void A(int i)
    {
        try
        {
            B(i + 1);
        }
        catch (Exception ex)
        {
            if (ex.Message != "!")
                Console.WriteLine(ex);
            else throw;
        }
    }

    private static void B(int i)
    {
        throw new Exception("!");
    }
}

예외가 발생할 때까지 WinDbg에서 이것을 실행하고 다음을 사용하여 스택을 인쇄하면 !clrstack -i -a다음 프레임 만 표시됩니다 A.

003eef10 00a7050d [DEFAULT] Void App.Program.A(I4)

PARAMETERS:
  + int i  = 1

LOCALS:
  + System.Exception ex @ 0x23e3178
  + (Error 0x80004005 retrieving local variable 'local_1')

그러나 when다음 을 사용하도록 프로그램을 변경하면 :

catch (Exception ex) when (ex.Message != "!")
{
    Console.WriteLine(ex);
}

스택에는 B의 프레임 도 포함되어 있습니다 .

001af2b4 01fb05aa [DEFAULT] Void App.Program.B(I4)

PARAMETERS:
  + int i  = 2

LOCALS: (none)

001af2c8 01fb04c1 [DEFAULT] Void App.Program.A(I4)

PARAMETERS:
  + int i  = 1

LOCALS:
  + System.Exception ex @ 0x2213178
  + (Error 0x80004005 retrieving local variable 'local_1')

이 정보는 크래시 덤프를 디버깅 할 때 매우 유용 할 수 있습니다.


7
놀랍습니다. 하지 않음 throw;(반대 throw ex;) 무사뿐만 아니라 스택을 떠나? 부작용은 +1입니다. 나는 그것을 승인 할 수 있을지 모르겠지만, 그 기술에 대해 아는 것이 좋습니다.
Heinzi

13
이것은 잘못된 것이 아닙니다. 이것은 스택 추적을 참조하지 않고 스택 자체를 참조합니다. 디버거 (WinDbg)에서 스택을보고를 사용 했더라도 throw;스택이 해제되고 매개 변수 값이 손실됩니다.
Eli Arbel

1
이것은 덤프를 디버깅 할 때 매우 유용 할 수 있습니다.
Eli Arbel

3
@Heinzi 스택 추적을 약간 변경하고 많이 변경하는 다른 스레드 에서 내 대답을 참조하십시오 . throw;throw ex;
Jeppe Stig Nielsen

1
사용 throw하면 스택 추적이 약간 방해됩니다. throw와 반대로 사용할 때 줄 번호가 다릅니다 when.
Mike Zboray

7

예외가 발생하면 예외 처리의 첫 번째 단계 에서 스택을 해제 하기 전에 예외가 포착되는 위치를 식별 합니다. "catch"위치가 식별되면 모든 "finally"블록이 실행됩니다 (예외가 "finally"블록을 벗어나면 이전 예외의 처리가 중단 될 수 있음에 유의하십시오). 이 경우 코드는 "catch"에서 실행을 재개합니다.

"when"의 일부로 평가되는 함수 내에 중단 점이있는 경우 해당 중단 점은 스택 해제가 발생하기 전에 실행을 일시 중단합니다. 반대로 "catch"의 중단 점은 모든 finally핸들러가 실행 된 후에 만 ​​실행을 일시 중단 합니다.

마지막으로, foocall의 23 번과 27 번 bar줄과 23 번 줄의 호출이 foo57 번 줄 에서 포착되어 다시 throw되는 예외를 throw하는 경우 스택 추적은 bar57 번 줄에서 호출하는 동안 예외가 발생했음을 제안합니다 [재 투입 위치] , 라인 23 또는 라인 27 호출에서 예외가 발생했는지 여부에 대한 정보를 삭제합니다. when처음에 예외 포착을 피하기 위해 사용 하면 그러한 방해를 피할 수 있습니다.

BTW, C #과 VB.NET 모두에서 귀찮게 어색한 유용한 패턴은 when절 내에서 함수 호출을 사용하여 절 내에서 사용할 수있는 변수를 설정 finally하여 함수가 정상적으로 완료되었는지 여부를 확인하고 함수가 발생한 경우를 처리하는 것입니다. 발생하는 모든 예외를 "해결"할 희망이 없지만 그럼에도 불구하고 이에 따라 조치를 취해야합니다. 예를 들어, 리소스를 캡슐화하는 객체를 반환해야하는 팩토리 메서드 내에서 예외가 발생하면 획득 한 모든 리소스를 해제해야하지만 기본 예외는 호출자에게 영향을 주어야합니다. 이를 의미 론적으로 처리하는 가장 깨끗한 방법은 (구문 적으로는 아니지만)finally블록은 예외가 발생했는지 확인하고, 그렇다면 더 이상 반환되지 않을 객체를 대신하여 획득 한 모든 리소스를 해제합니다. 정리 코드는 예외를 일으킨 조건을 해결할 희망이 없기 때문에 실제로는 catch안되지만 무슨 일이 일어 났는지 알면됩니다. 다음과 같은 함수 호출 :

bool CopySecondArgumentToFirstAndReturnFalse<T>(ref T first, T second)
{
  first = second;
  return false;
}

when절 내 에서 팩토리 기능이 어떤 일이 발생했음을 알 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.