programing

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

nasanasas 2020. 9. 8. 08:04
반응형

왜 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가 허용됩니다.
  • All local variables in the iterator block are instance variables in the generated type, so you can freely move code to new methods

Now transform:

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

into (sort of pseudo-code):

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

The only duplication is in setting up try/catch blocks - but that's something the compiler can certainly do.

I may well have missed something here - if so, please let me know!


All the yield statements in an iterator definition are converted to a state in a state machine which effectively uses a switch statement to advance states. If it did generate code for yield statements in a try/catch it would have to duplicate everything in the try block for each yield statement while excluding every other yield statement for that block. This isn't always possible, particularly if one yield statement is dependant on an earlier one.


I would speculate that because of the way the call stack gets wound/unwound when you yield return from an enumerator it becomes impossible for a try/catch block to actually "catch" the exception. (because the yield return block is not on the stack, even though he originated the iteration block)

To get an ideea of what I'm talking about setup an iterator block and a foreach using that iterator. Check what the Call Stack looks like inside the foreach block and then check it inside the iterator try/finally block.


I've accepted THE INVINCIBLE SKEET's answer until someone from Microsoft comes along to pour cold water on the idea. But I don't agree with the matter-of-opinion part - of course a correct compiler is more important than a complete one, but the C# compiler is already very clever in sorting out this transformation for us as far as it does. A little more completeness in this case would make the language easier to use, teach, explain, with fewer edge cases or gotchas. So I think it would be worth the extra effort. A few guys in Redmond scratch their heads for a fortnight, and as a result millions of coders over the next decade can relax a little more.

(I also harbour a sordid desire for there to be a way to make yield return throw an exception that has been stuffed into the state machine "from the outside", by the code driving the iteration. But my reasons for wanting this are quite obscure.)

Actually one query I have about Jon's answer is to do with the yield return expression throwing.

Obviously yield return 10 isn't so bad. But this would be bad:

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

So wouldn't it make more sense to evaluate this inside the preceeding try/catch block:

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;

The next problem would be nested try/catch blocks and rethrown exceptions:

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

But I'm sure it's possible...


for those using Unity:

yield return new WaitForSeconds(startWait);
while (numWaves < 4 && _myPauseState)
{
for (int i = 0; i < hazardCount;)
{
//spawn code
}
yield return new WaitForSeconds(waveWait);
numWaves++;
}

is actually possible inside of a ienumerator

참고URL : https://stackoverflow.com/questions/346365/why-cant-yield-return-appear-inside-a-try-block-with-a-catch

반응형