Подтвердить что ты не робот

С#: возврат результата в пределах foreach не удался - тело не может быть блоком итератора

Рассмотрим этот бит обфускации кода. Цель состоит в том, чтобы создать новый объект "на лету" через анонимный конструктор и yield return его. Цель состоит в том, чтобы избежать необходимости поддерживать локальную коллекцию только для просто return.

public static List<DesktopComputer> BuildComputerAssets()
{           
    List<string> idTags = GetComputerIdTags();

    foreach (var pcTag in idTags)
    {
        yield return new DesktopComputer() {AssetTag= pcTag
                                          , Description = "PC " + pcTag
                                          , AcquireDate = DateTime.Now
                                           };
    }            
}

К сожалению, этот бит кода создает исключение:

Ошибка 28 Тело "Foo.BuildComputerAssets()" не может быть блоком итератора, потому что "System.Collections.Generic.List" не является типом интерфейса итератора

Вопросы

  • Что означает это сообщение об ошибке?
  • Как я могу избежать этой ошибки и правильно использовать yield return?
4b9b3361

Ответ 1

Вы можете использовать yield return только в функции, которая возвращает IEnumerable или IEnumerator, а не List<T>.

Вам нужно изменить свою функцию, чтобы вернуть IEnumerable<DesktopComputer>.

В качестве альтернативы вы можете переписать функцию для использования List<T>.ConvertAll:

return GetComputerIdTags().ConvertAll(pcTag => 
    new DesktopComputer() {
        AssetTag    = pcTag,
        Description = "PC " + pcTag,
        AcquireDate = DateTime.Now
    });

Ответ 2

Ваша подпись метода неверна. Это должно быть:

public static IEnumerable<DesktopComputer> BuildComputerAssets()

Ответ 3

yield работает только с типами Iterator:

Оператор yield может отображаться только внутри блока итератора

Итераторы определяются как

Возвращаемый тип итератора должен быть IEnumerable, IEnumerator, IEnumerable <T> , или IEnumerator <T> .

IList и IList <T> do реализуют IEnumerable/IEnumerable <T> , но каждый вызывающий объект для перечислителя ожидает один из четырех типов выше и не более того.

Ответ 4

Вы также можете реализовать те же функции, используя запрос LINQ (в С# 3.0+). Это менее эффективно, чем метод ConvertAll, но он более общий. Позже вам также может понадобиться использовать другие функции LINQ, такие как фильтрация:

return (from pcTag in GetComputerIdTags()
        select new DesktopComputer() { 
          AssetTag    = pcTag, 
          Description = "PC " + pcTag, 
          AcquireDate = DateTime.Now 
        }).ToList();

Метод ToList преобразует результат от IEnumerable<T> в List<T>. Мне лично не нравится ConvertAll, потому что он делает то же, что и LINQ. Но поскольку он был добавлен ранее, его нельзя использовать с LINQ (его следовало называть Select).