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

다음은 괜찮습니다.

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 입니다. 그래도 이유는 설명하지 않습니다.



답변

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

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

  • 일반 속성이 될 수 없음
  • 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 블록을 설정하는 것입니다.하지만 컴파일러가 확실히 할 수있는 일입니다.

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


답변

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


답변

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

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


답변

나는 마이크로 소프트의 누군가가 아이디어에 찬물을 부을 때까지 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");

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


답변