왜 yield return이 catch가있는 try 블록 안에 나타나지 않습니까?


95

다음은 괜찮습니다.

try
{
    Console.WriteLine("Before");

    yield return 1;

    Console.WriteLine("After");
}
finally
{
    Console.WriteLine("Done");
}

finally블록은 모든 것을 실행이 완료 될 때 (실행 IEnumerator<T>지원하는 IDisposable이 완료되기 전에 열거가 중단 된 경우에도이를 보장 할 수있는 방법을 제공하기 위해).

그러나 이것은 좋지 않습니다.

try
{
    Console.WriteLine("Before");

    yield return 1;  // error CS1626: Cannot yield a value in the body of a try block with a catch clause

    Console.WriteLine("After");
}
catch (Exception e)
{
    Console.WriteLine(e.Message);
}

(인수를 위해) WriteLinetry 블록 내의 하나 이상의 호출에 의해 예외가 발생한다고 가정 합니다. catch블록 에서 실행을 계속하는 데 문제가 있습니까?

물론, 항복 반환 부분은 무엇을 던질 (현재) 수없는, 그러나 왜 둘러싸을 가진로부터의 정지 우리가해야 try/ catch전이나 후에 발생한 예외를 처리 할 yield return?

업데이트 : Eric Lippert흥미로운 의견이 있습니다. 이미 try / finally 동작을 올바르게 구현하는 데 충분한 문제가있는 것 같습니다!

편집 :이 오류에 대한 MSDN 페이지는 http://msdn.microsoft.com/en-us/library/cs1x15az.aspx 입니다. 그래도 이유는 설명하지 않습니다.


2
Eric Lippert의 의견에 대한 직접 링크 : blogs.msdn.com/oldnewthing/archive/2008/08/14/…
Roman Starkov 2009

참고 : catch 블록 자체에서도 양보 할 수 없습니다. :-(
Simon_Weaver

2
oldnewthing 링크가 더 이상 작동하지 않습니다.
세바스찬 REDL

답변:


50

나는 이것이 타당성보다는 실용성의 문제라고 생각합니다. 이 제한이 실제로 해결할 수없는 문제인 경우는 거의 없다고 생각합니다.하지만 컴파일러의 복잡성이 추가되면 매우 중요합니다.

내가 이미 접한 이와 같은 몇 가지가 있습니다.

  • 일반 속성이 될 수 없음
  • X가 XY (X의 중첩 클래스)에서 파생되지 않음
  • 생성 된 클래스에서 공용 필드를 사용하는 반복기 블록

이러한 각각의 경우 컴파일러의 추가 복잡성을 희생하면서 조금 더 자유를 얻을 수 있습니다. 팀은 실용적인 선택을했습니다. 저는 그들에게 박수를 보냅니다. 저는 99.9 % 정확한 컴파일러 (예, 버그가 있습니다. 저번에 하나를 만났습니다)로 약간 더 제한적인 언어를 사용하고 싶습니다. 제대로 컴파일 할 수없는 유연한 언어.

편집 : 왜 그것이 실현 가능한지에 대한 의사 증명이 있습니다.

다음을 고려하십시오.

  • 수율 반환 부분 자체에서 예외가 발생하지 않도록 할 수 있습니다 (값을 미리 계산 한 다음 필드를 설정하고 "true"를 반환 함).
  • 반복기 블록에서 yield return을 사용하지 않는 try / catch가 허용됩니다.
  • 반복자 블록의 모든 로컬 변수는 생성 된 유형의 인스턴스 변수이므로 코드를 새 메서드로 자유롭게 이동할 수 있습니다.

이제 변형 :

try
{
    Console.WriteLine("a");
    yield return 10;
    Console.WriteLine("b");
}
catch (Something e)
{
    Console.WriteLine("Catch block");
}
Console.WriteLine("Post");

(일종의 의사 코드) :

case just_before_try_state:
    try
    {
        Console.WriteLine("a");
    }
    catch (Something e)
    {
        CatchBlock();
        goto case post;
    }
    __current = 10;
    return true;

case just_after_yield_return:
    try
    {
        Console.WriteLine("b");
    }
    catch (Something e)
    {
        CatchBlock();
    }
    goto case post;

case post;
    Console.WriteLine("Post");


void CatchBlock()
{
    Console.WriteLine("Catch block");
}

유일한 중복은 try / catch 블록을 설정하는 것입니다.하지만 컴파일러가 확실히 할 수있는 일입니다.

여기서 놓친 것이있을 수도 있습니다. 그렇다면 알려주세요!


11
좋은 개념 증명하지만,이 전략은 고통스러운 얻는다 (아마도 더 C # 컴파일러 작가에 대한보다 C #을 프로그래머)를 일단 같은 것들로 범위 만들기를 시작 using하고를 foreach. 예 :try{foreach (string s in c){yield return s;}}catch(Exception){}
Brian

"try / catch"의 일반적인 의미는 예외로 인해 try / catch 블록의 일부를 건너 뛰면 적절한 "catch"블록이있는 경우 제어가 전송된다는 것을 의미합니다. 불행히도 수익률 반환 "중에"예외가 발생하면 소유자가 관심있는 모든 데이터를 검색했기 때문에 예외로 인해 폐기되는 경우와 폐기되는 경우를 반복자가 구별 할 방법이 없습니다.
supercat

7
"이 제한이 실제로 해결할 수없는 문제인 경우는 거의 없을 것 같습니다."C에서 일반적으로 사용되는 오류 코드 반환 전략을 사용할 수 있기 때문에 예외가 필요하지 않다고 말하는 것과 같습니다. 몇 년 전. 기술적 인 어려움이 중요 할 수 있음을 인정하지만 yield,이 문제를 해결하기 위해 작성해야하는 스파게티 코드 때문에 제 생각에는 여전히 .
jpmc26

@ jpmc26 : 아니요, 정말 그렇게 말하는 것과는 다릅니다. 나는이 기억이 안나요 지금까지 나를 물고, 나는 시간의 반복자 블록을 많이 사용했습니다. 그것은 IMO 의 유용성을 약간 제한합니다 . 심각하게 . yield
Jon Skeet 2013 년

2
이 '기능'은 실제로 해결하기 위해 어떤 경우에는 다소 추악한 코드가 필요합니다. stackoverflow.com/questions/5067188/…
namey

5

yield반복기 정의의 모든 문은 상태 switch를 향상시키기 위해 문을 효과적으로 사용하는 상태 머신의 상태로 변환됩니다 . 이 경우 에 대한 코드 생성 yield시도 / 캐치에 문이 복제해야합니다 모든 것을try대한 블록을 각각 yield 다른 모든 제외하면서 문 yield이 블록의 문을. 특히 한 yield문장이 이전 문장에 의존하는 경우 항상 가능한 것은 아닙니다 .


2
나는 그것을 사지 않는다고 생각한다. 나는 그것이 완전히 실현 가능할 것이라고 생각하지만 매우 복잡합니다.
Jon Skeet

2
C #의 Try / catch 블록은 재진입을 의미하지 않습니다. 분할하면 예외 후 MoveNext ()를 호출하고 유효하지 않은 상태로 try 블록을 계속할 수 있습니다.
Mark Cidade

2

열거 자에서 반환 값을 반환 할 때 호출 스택이 감기거나 풀리는 방식 때문에 try / catch 블록이 실제로 예외를 "catch"하는 것이 불가능 해집니다. (그가 반복 블록을 시작 했음에도 불구하고 yield return 블록이 스택에 없기 때문에)

내가 말하는 것에 대한 아이디어를 얻으려면 반복기를 사용하여 반복기 블록과 foreach를 설정하십시오. foreach 블록 내부에서 Call Stack이 어떻게 보이는지 확인한 다음 iterator try / finally 블록 내부에서 확인합니다.


C ++의 스택 해제에 익숙합니다. 여기서 소멸자는 범위를 벗어나는 로컬 개체에 대해 호출됩니다. C #에서 해당하는 것은 try / finally입니다. 그러나 그 풀림은 수익률 반환이 발생할 때 발생하지 않습니다. 그리고 try / catch의 경우 수익률과 상호 작용할 필요가 없습니다.
Daniel Earwicker

반복자를 반복 할 때 호출 스택에 어떤 일이 발생하는지 확인하면 내가 의미하는 바를 이해할 수있을 것입니다.
Radu094

@ Radu094 : 아니요, 가능할 것이라고 확신합니다. 이미 최종적으로 처리된다는 것을 잊지 마십시오. 적어도 다소 유사합니다.
Jon Skeet

2

나는 마이크로 소프트의 누군가가 아이디어에 찬물을 부을 때까지 INVINCIBLE SKEET의 대답을 받아 들였다. 그러나 나는 의견의 문제에 동의하지 않습니다. 물론 올바른 컴파일러가 완전한 컴파일러보다 더 중요하지만 C # 컴파일러는 이미 우리를 위해이 변환을 분류하는 데 매우 영리합니다. 이 경우에 조금 더 완벽하면 언어를 더 쉽게 사용하고, 가르치고, 설명 할 수 있으며, 더 적은 엣지 케이스 나 문제를 겪을 수 있습니다. 그래서 더 노력할 가치가 있다고 생각합니다. 레드몬드의 몇몇 사람들은 2 주 동안 머리를 긁적이며, 그 결과 다음 10 년 동안 수백만 명의 코더들이 좀 더 긴장을 풀 수 있습니다.

(저는 또한 yield return반복을 유도하는 코드에 의해 "외부에서"상태 머신에 채워진 예외를 던지는 방법이 있다는 끔찍한 욕망을 품고 있습니다 . 그러나 이것을 원하는 이유는 매우 모호합니다.)

실제로 Jon의 대답에 대한 한 가지 쿼리는 yield return expression throwing과 관련된 것입니다.

분명히 수익률 10은 그렇게 나쁘지 않습니다. 그러나 이것은 나쁠 것입니다.

yield return File.ReadAllText("c:\\missing.txt").Length;

따라서 선행 try / catch 블록 내에서 이것을 평가하는 것이 더 합리적이지 않을 것입니다.

case just_before_try_state:
    try
    {
        Console.WriteLine("a");
        __current = File.ReadAllText("c:\\missing.txt").Length;
    }
    catch (Something e)
    {
        CatchBlock();
        goto case post;
    }
    return true;

다음 문제는 중첩 된 try / catch 블록과 예외가 다시 발생하는 것입니다.

try
{
    Console.WriteLine("x");

    try
    {
        Console.WriteLine("a");
        yield return 10;
        Console.WriteLine("b");
    }
    catch (Something e)
    {
        Console.WriteLine("y");

        if ((DateTime.Now.Second % 2) == 0)
            throw;
    }
}
catch (Something e)
{
    Console.WriteLine("Catch block");
}
Console.WriteLine("Post");

하지만 가능하다고 확신합니다 ...


1
예, 평가를 try / catch에 넣습니다. 변수 설정을 어디에 두든 상관 없습니다. 요점은 수익률 수익률이있는 단일 try / catch를 수익률 수익률이있는 두 개의 try / catch로 효과적으로 나눌 수 있다는 것입니다.
Jon Skeet
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.