Блоки итераторов, Часть четвёртая: Почему нет yield в блоках catch?


Теперь, когда вы знаете, почему мы не разрешаем yield в finally, легко увидеть, почему мы также запрещаем yield в catch.

Во-первых, у нас всё еще есть проблема – запрещено делать goto в середину обработчика региона, защищённого try. Единственный способ войти в блок catch – это «нелокальный переход», то есть отлов исключения. Так что, стоило вам сделать yield из блока catch, как при следующем вызове MoveNext мы не смогли бы вернуться внутрь блока catch, откуда мы вышли в прошлый раз.

Во-вторых, исключение, которое было брошено и поймано, является неявной частью исполнения блока catch, потому что оно может быть повторно выброшено при помощи синтаксиса «пустого throw». У нас нет способа сохранить это состояние между вызовами MoveNext.

Я полагаю, что теоретически мы могли разрешить yield из блоков catch, в которых нет «пустого throw». Мы могли бы превращать это:

try
{
  M();
}
catch(Exception x)
{
  yield return x.ToString().Length;
  yield return 123;
}

в

switch(this.state)
{
case 0: goto LABEL0;
case 1: goto LABEL1;
case 2: goto LABEL2;
}
LABEL0:
try
{
  M();
  this.state = 2;
  goto LABEL2;
}
catch(Exception x)
{
  this.x = x; 
}
this.current = this.x.ToString().Length;
this.state = 1;
return true;
LABEL1:
this.current = 123;
this.state = 2;
return true;
LABEL2:
return false;

Но, честно говоря, это довольно криво. Запрет фундаментальной части синтаксиса catch казался бы весьма странным ограничением.

И, на самом деле, какой сценарий является мотивом для этой ситуации? Вправду ли люди хотят попробовать что-то, а затем выдать пачку результатов, если это не сработает? Предполагаю, вы могли бы сделать что-то вроде:

IEnumerable<string> GetFooStrings(Foo foo)
{
  try
  {
    yield return Foo.Bar();
  }
  catch(InvalidFooException x)
  {
    yield return "";
  }
… и.т.д.

но есть масса способов лего переписать это так, чтобы вам не нужно было делать yield в catch:

IEnumerable<string> GetFooStrings(Foo foo)
{
  string result;
  try
  {
    result = Foo.Bar();
  }
  catch(InvalidFooException x)
  {
    result = "";
  }
  yield return result;
… etc.

Поскольку обычно есть простые способы обхода этого запрета, а нам бы пришлось наложить ограничения сумасшедшего вида на использование повторных throw, то проще просто сказть «никаких yield внутри catch».

Мы всё ещё не объяснили, ни почему запрещено делать yield внутри try, у которого есть catch, ни почему разрешено делать yield внутри try, у которого есть finally. Чтобы понять, почему, в следующий раз мне придётся погрузиться в размышления о дуализме последовательностей и событий.

Skip to main content