Почему невозможно получить доход, появившийся внутри блока try с catch?

В порядке:

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

Предположим (ради аргумента), что исключение вызывается одним или другим вызовом WriteLine внутри блока try. Какая проблема с продолжением выполнения в блоке catch?

Конечно, часть возврата доходности (в настоящее время) не может ничего выбросить, но почему это должно помешать нам иметь закрывающий try/catch для обработки исключений, которые были выбраны до или после yield return?

Обновление: Там интересный комментарий от Эрика Липперта здесь - кажется, что у них уже достаточно проблем с реализацией попытки/окончательно корректно!

EDIT: на странице MSDN этой ошибки: http://msdn.microsoft.com/en-us/library/cs1x15az.aspx. Однако он не объясняет, почему.

Ответ 1

Я подозреваю, что это вопрос практичности, а не выполнимость. Я подозреваю, что очень, очень немногие времена, когда это ограничение на самом деле является проблемой, с которой нельзя обойтись, - но добавленная сложность в компиляторе была бы очень значительной.

Есть несколько таких вещей, которые я уже встречал:

  • Атрибуты не могут быть общими
  • Неспособность для X выводиться из X.Y(вложенный класс в X)
  • Итератор блокирует использование общедоступных полей в сгенерированных классах

В каждом из этих случаев можно было бы получить немного больше свободы за счет дополнительной сложности в компиляторе. Команда сделала прагматичный выбор, за который я их приветствую - я бы предпочел немного более ограничительный язык с точным компилятором на 99,9% (да, есть ошибки, я столкнулся с одним на SO только на днях), чем больше гибкий язык, который не мог правильно скомпилироваться.

РЕДАКТИРОВАТЬ: Здесь псевдо-доказательство того, почему это возможно.

Рассмотрим, что:

  • Вы можете убедиться, что сама часть возвращаемой доходности не генерирует исключение (предварительно вычисляйте значение, а затем вы просто устанавливаете поле и возвращаете "true" )
  • Вам разрешен try/catch, который не использует return return в блоке итератора.
  • Все локальные переменные в блоке итератора являются переменными экземпляра в сгенерированном типе, поэтому вы можете свободно перемещать код в новые методы.

Теперь преобразуем:

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 - но это то, что, безусловно, может сделать компилятор.

Возможно, я пропустил что-то здесь - если да, то сообщите мне!

Ответ 2

Все операторы yield в определении итератора преобразуются в состояние в конечном компьютере, которое эффективно использует оператор switch для продвижения состояний. Если бы он генерировал код для операторов yield в try/catch, ему пришлось бы дублировать все в блоке try для каждого оператора yield, исключая все остальные инструкции yield для этого блока. Это не всегда возможно, особенно если один оператор yield зависит от более раннего.

Ответ 3

Я бы предположил, что из-за того, что стек вызовов получает рану/разматывание, когда вы возвращаете результат из перечислителя, невозможно, чтобы блок try/catch фактически "поймал" исключение. (потому что блок возврата доходности не находится в стеке, хотя он и создал итерационный блок)

Чтобы получить представление о том, что я говорю о настройке блока итератора и foreach, используя этот итератор. Посмотрите, как выглядит стек вызовов внутри блока foreach, а затем проверьте его внутри блока try/finally итератора.

Ответ 4

Я принял ответ INVINCIBLE SKEET, пока кто-то из Microsoft не придет, чтобы налить холодную воду на эту идею. Но я не согласен с частью вопроса - конечно, правильный компилятор более важен, чем полный, но компилятор С# уже очень умен, сортируя это преобразование для нас, насколько это возможно. Немного более полнота в этом случае облегчит использование языка, научит, объяснит, с меньшим количеством краевых случаев или gotchas. Поэтому я думаю, что это будет стоить дополнительных усилий. Несколько парней в Редмонде почесывают головы на две недели, и в результате миллионы кодеров в течение следующего десятилетия могут немного расслабиться.

(Я также жалею о том, что существует способ сделать yield return исключение, которое было добавлено в конечный автомат "снаружи", кодом, управляющим итерацией. Но мои причины для желания это довольно неясно.)

На самом деле один вопрос, который у меня есть о ответе Джона, заключается в том, чтобы сделать бросок выражения return return.

Очевидно, что доходность 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");

Но я уверен, что это возможно...

Ответ 5

для тех, кто использует Unity:

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

действительно возможно внутри ienumerator