Почему возврат не может появиться внутри блока 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 ответов:
Я подозреваю, что это вопрос практичности, а не целесообразности. Я подозреваю, что есть очень, очень мало раз, когда это ограничение на самом деле проблема, которую нельзя обойти-но добавленная сложность в компиляторе будет очень значительной.
есть несколько вещей, как это, что я уже сталкивался:
- атрибуты не могут быть родовыми
- невозможность для 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");
но я уверен, что это возможно...