Почему нельзя использовать «return» и «yield return» в одном методе? - PullRequest
20 голосов
/ 09 марта 2012

Почему мы не можем использовать возврат и возврат дохода в одном и том же методе?

Например, у нас могут быть GetIntegers1 и GetIntegers2 ниже, но не GetIntegers3.

public IEnumerable<int> GetIntegers1()
{
  return new[] { 4, 5, 6 };
}

public IEnumerable<int> GetIntegers2()
{
  yield return 1;
  yield return 2;
  yield return 3;
}

public IEnumerable<int> GetIntegers3()
{
  if ( someCondition )
  {
    return new[] {4, 5, 6}; // compiler error
  }
  else
  {
    yield return 1;
    yield return 2;
    yield return 3;
  }
}

Ответы [ 5 ]

17 голосов
/ 09 марта 2012

return стремится. Возвращает весь набор результатов одновременно. yield return создает перечислитель. За кулисами компилятор C # испускает необходимый класс для перечислителя, когда вы используете yield return. Компилятор не ищет условия выполнения, такие как if ( someCondition ), при определении того, должен ли он генерировать код для перечислимого объекта или иметь метод, который возвращает простой массив. Он обнаруживает, что в вашем методе вы используете оба, что невозможно, так как он не может генерировать код для перечислителя и в то же время метод возвращает нормальный массив, и все это для того же метода.

11 голосов
/ 09 марта 2012

Нет, вы не можете этого сделать - блок итератора (что-то с yield) не может использовать обычный (без выхода) return. Вместо этого вам нужно использовать 2 метода:

public IEnumerable<int> GetIntegers3()
{
  if ( someCondition )
  {
    return new[] {4, 5, 6}; // compiler error
  }
  else
  {
    return GetIntegers3Deferred();
  }
}
private IEnumerable<int> GetIntegers3Deferred()
{
    yield return 1;
    yield return 2;
    yield return 3;
}

или с в данном конкретном случае код для обоих уже существует в двух других методах:

public IEnumerable<int> GetIntegers3()
{
  return ( someCondition ) ? GetIntegers1() : GetIntegers2();
}
8 голосов
/ 09 марта 2012

Компилятор перезаписывает любые методы с помощью оператора yield (возврат или прерывание).В настоящее время он не может обрабатывать методы, которые могут или не могут yield.

Я бы порекомендовал прочесть главу 6 C # в глубине Джона Скита, из которых глава 6 доступна бесплатно - она ​​довольно красиво охватывает блоки итераторов.* Я не вижу причин, почему это не будет возможно в будущих версиях компилятора c #.Другие языки .Net поддерживают нечто подобное в форме оператора yield from ( См. F# yield!).Если бы такой оператор существовал в c #, это позволило бы вам написать свой код в виде:

public IEnumerable<int> GetIntegers()
{
  if ( someCondition )
  {
    yield! return new[] {4, 5, 6};
  }
  else
  {
    yield return 1;
    yield return 2;
    yield return 3;
  }
}
3 голосов
/ 09 марта 2012

Теоретически я думаю, что нет никаких причин, по которым возврат и возврат не могут быть смешаны: компилятору было бы легко сначала синтаксически преобразовать любое предложение return (blabla()); в:

var myEnumerable = blabla();
foreach (var m in myEnumerable) 
    yield return m; 
yield break; 

изатем продолжайте (чтобы преобразовать весь метод в ... во что бы то ни стало теперь, во внутренний анонимный класс IEnumerator?!)

Так почему же они не решили реализовать его, вот два предположения:

  • они, возможно, решили, что пользователям было бы непонятно иметь одновременно возврат и доходность,

  • Возвращение всего перечисляемого быстрее и дешевлено также стремится;Построение с помощью return yield немного дороже (особенно если вызывать рекурсивно, см. предупреждение Эрика Липперта об обходе в двоичных деревьях с инструкциями yield return: например, https://stackoverflow.com/a/3970171/671084), но лениво.Таким образом, пользователь обычно не хотел бы смешивать это: если вам не нужна лень (то есть вы знаете всю последовательность), не теряйте штраф за эффективность, просто используйте обычный метод.Возможно, они хотели заставить пользователя мыслить так:

С другой стороны, кажется, что существуют ситуации, когда пользователь может извлечь выгоду из некоторых синтаксических расширений;Возможно, вы захотите прочитать этот вопрос и ответы в качестве примера (не тот же вопрос, но тот, который, вероятно, с похожим мотивом): Доходность Возвращает много?

0 голосов
/ 09 марта 2012

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

Что именно сделает ваш код? Будет ли он напрямую возвращать массив или итерировать по нему?

Если бы он непосредственно возвращал массив, вам пришлось бы придумывать сложные правила, при каких условиях return допускается, потому что return после yield return не имеет смысла. И вам, вероятно, потребуется сгенерировать сложный код, чтобы решить, будет ли метод возвращать пользовательский итератор или массив.

Если вы хотите выполнить итерацию коллекции, вам, вероятно, понадобится более подходящее ключевое слово. Что-то вроде yield foreach. Это на самом деле рассматривалось, но в конечном итоге не было выполнено. Я думаю, что помню, что читая основную причину, это то, что на самом деле очень трудно заставить его работать хорошо, если у вас есть несколько вложенных итераторов.

...