Почему нельзя вернуть return в блоке try с уловом? - PullRequest
87 голосов
/ 06 декабря 2008

Все в порядке:

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?

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

РЕДАКТИРОВАТЬ: страница MSDN на эту ошибку: http://msdn.microsoft.com/en-us/library/cs1x15az.aspx. Хотя это не объясняет, почему.

Ответы [ 5 ]

48 голосов
/ 07 декабря 2008

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

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

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

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

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

Считайте, что:

  • Вы можете убедиться, что сама часть возвращаемого результата не выдает исключение (предварительно рассчитайте значение, а затем вы просто устанавливаете поле и возвращаете "true")
  • Вам разрешено использовать метод try / catch, который не использует yield 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, но компилятор, безусловно, может это сделать.

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

5 голосов
/ 06 декабря 2008

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

2 голосов
/ 07 декабря 2008

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

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

На самом деле, один мой вопрос об ответе Джона связан с бросанием выражения yield 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");

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

2 голосов
/ 06 декабря 2008

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

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

0 голосов
/ 16 февраля 2014

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

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

фактически возможно внутри счетчика

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...