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’기능을 사용하면 핸들러를 건너 뛰고 핸들러 내에서 특정 사용 사례를 처리 할 때보 다 훨씬 빨리 스택 해제가 발생할 수 있으므로 예외 처리가 더 빨라 집니까? 사람들이 모범 사례로 채택 할 수있는이 기능에 더 적합한 특정 사용 사례가 있습니까?
답변
캐치 블록을 사용하면 이미 예외 유형 을 필터링 할 수 있습니다 .
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
조건 없이는 불가능 합니다.
답변
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')
이 정보는 크래시 덤프를 디버깅 할 때 매우 유용 할 수 있습니다.
답변
예외가 발생하면 예외 처리의 첫 번째 단계 에서 스택을 해제 하기 전에 예외가 포착되는 위치를 식별 합니다. “catch”위치가 식별되면 모든 “finally”블록이 실행됩니다 (예외가 “finally”블록을 벗어나면 이전 예외의 처리가 중단 될 수 있음에 유의하십시오). 이 경우 코드는 “catch”에서 실행을 재개합니다.
“when”의 일부로 평가되는 함수 내에 중단 점이있는 경우 해당 중단 점은 스택 해제가 발생하기 전에 실행을 일시 중단합니다. 반대로 “catch”의 중단 점은 모든 finally
핸들러가 실행 된 후에 만 실행을 일시 중단 합니다.
마지막으로, foo
call의 23 번과 27 번 bar
줄과 23 번 줄의 호출이 foo
57 번 줄 에서 포착되어 다시 throw되는 예외를 throw하는 경우 스택 추적은 bar
57 번 줄에서 호출하는 동안 예외가 발생했음을 제안합니다 [재 투입 위치] , 라인 23 또는 라인 27 호출에서 예외가 발생했는지 여부에 대한 정보를 삭제합니다. when
처음에 예외 포착을 피하기 위해 사용 하면 그러한 방해를 피할 수 있습니다.
BTW, C #과 VB.NET 모두에서 귀찮게 어색한 유용한 패턴은 when
절 내에서 함수 호출을 사용하여 절 내에서 사용할 수있는 변수를 설정 finally
하여 함수가 정상적으로 완료되었는지 여부를 확인하고 함수가 발생한 경우를 처리하는 것입니다. 발생하는 모든 예외를 “해결”할 희망이 없지만 그럼에도 불구하고 이에 따라 조치를 취해야합니다. 예를 들어, 리소스를 캡슐화하는 객체를 반환해야하는 팩토리 메서드 내에서 예외가 발생하면 획득 한 모든 리소스를 해제해야하지만 기본 예외는 호출자에게 영향을 주어야합니다. 이를 의미 론적으로 처리하는 가장 깨끗한 방법은 (구문 적으로는 아니지만)finally
블록은 예외가 발생했는지 확인하고, 그렇다면 더 이상 반환되지 않을 객체를 대신하여 획득 한 모든 리소스를 해제합니다. 정리 코드는 예외를 일으킨 조건을 해결할 희망이 없기 때문에 실제로는 catch
안되지만 무슨 일이 일어 났는지 알면됩니다. 다음과 같은 함수 호출 :
bool CopySecondArgumentToFirstAndReturnFalse<T>(ref T first, T second)
{
first = second;
return false;
}
when
절 내 에서 팩토리 기능이 어떤 일이 발생했음을 알 수 있습니다.