Почему возврат не может появиться внутри блока try с уловом?


в следующем порядке:

try
{
    Console.WriteLine("Before");

    yield return 1;

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

The 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 83

5 ответов:

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

есть несколько вещей, как это, что я уже сталкивался:

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

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

EDIT: вот псевдо-доказательство того, как это возможно.

считаю, что:

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

теперь преобразование:

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 / catch он должен был бы дублировать все на try блок каждогоyield заявление, исключая все остальные yield заявление для этого блока. Это не всегда возможно, особенно если один yield утверждение зависит от более раннего.

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

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

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

(Я также питаю отвратительное желание, чтобы был способ сделать 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");

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

для тех, кто использует 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