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