Зачем использовать ключевое слово yield, когда я могу просто использовать обычный IEnumerable?

С учетом этого кода:

IEnumerable<object> FilteredList()
{
    foreach( object item in FullList )
    {
        if( IsItemInPartialList( item ) )
            yield return item;
    }
}

Почему я не должен просто его кодировать так:

IEnumerable<object> FilteredList()
{
    var list = new List<object>(); 
    foreach( object item in FullList )
    {
        if( IsItemInPartialList( item ) )
            list.Add(item);
    }
    return list;
}

Я понимаю, что делает ключевое слово yield. Он сообщает компилятору, что он создает определенную вещь (итератор). Но зачем это использовать? Помимо того, что это немного меньше кода, что он делает для меня?

Ответ 1

Использование yield делает коллекцию ленивой.

Скажем, вам нужны только первые пять предметов. Твой путь, я должен пройти весь список, чтобы получить первые пять предметов. С yield я прохожу только через первые пять элементов.

Ответ 2

Преимущество блоков итератора заключается в том, что они работают лениво. Поэтому вы можете написать способ фильтрации следующим образом:

public static IEnumerable<T> Where<T>(this IEnumerable<T> source,
                                   Func<T, bool> predicate)
{
    foreach (var item in source)
    {
        if (predicate(item))
        {
            yield return item;
        }
    }
}

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

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

public static IEnumerable<int> RandomSequence(int minInclusive, int maxExclusive)
{
    Random rng = new Random();
    while (true)
    {
        yield return rng.Next(minInclusive, maxExclusive);
    }
}

Как бы вы сохранили бесконечную последовательность в списке?

My Серия блога Edulinq дает примерную реализацию LINQ to Objects, которая сильно использует блоки итераторов. LINQ в основном ленив, где это может быть - и помещение вещей в список просто не работает.

Ответ 3

С кодом "список" вам необходимо обработать полный список, прежде чем вы сможете передать его на следующий шаг. Версия "yield" немедленно передает обрабатываемый элемент на следующий шаг. Если этот "следующий шаг" содержит ".Take(10)", то версия "yield" будет обрабатывать только первые 10 предметов и забывать обо всех остальных. Код "list" обработал бы все.

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

Ответ 4

Вы можете использовать yield для возврата элементов, отсутствующих в списке. Вот небольшой образец, который может бесконечно перебирать список до отмены.

public IEnumerable<int> GetNextNumber()
{
    while (true)
    {
        for (int i = 0; i < 10; i++)
        {
            yield return i;
        }
    }
}

public bool Canceled { get; set; }

public void StartCounting()
{
    foreach (var number in GetNextNumber())
    {
        if (this.Canceled) break;
        Console.WriteLine(number);
    }
}

Это пишет

0
1
2
3
4
5
6
7
8
9
0
1
2
3
4

... и т.д.. на консоль до отмены.

Ответ 5

object jamesItem = null;
foreach(var item in FilteredList())
{
   if (item.Name == "James")
   {
       jamesItem = item;
       break;
   }
}
return jamesItem;

Когда вышеприведенный код используется для прокрутки FilteredList() и принятия элемента item.Name == "Джеймс" будет удовлетворен на втором элементе в списке, метод с использованием yield даст два раза. Это ленивое поведение.

Где, как метод, использующий список, добавит все n объектов в список и передаст полный список вызывающему методу.

Это как раз тот случай, когда разница между IEnumerable и IList может быть выделена.

Ответ 6

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

Рассмотрим следующий код:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(string.Join(", ", Fibonacci().Take(10)));
        Console.WriteLine(string.Join(", ", Fibonacci().Skip(15).Take(1)));
        Console.WriteLine(string.Join(", ", Fibonacci().Skip(10).Take(5)));
        Console.WriteLine(string.Join(", ", Fibonacci().Skip(100).Take(1)));
        Console.ReadKey();
    }

    private static IEnumerable<long> Fibonacci()
    {
        long a = 0;
        long b = 1;

        while (true)
        {
            long temp = a;
            a = b;

            yield return a;

            b = temp + b;
        }
    }
}

Это вернет:

1, 1, 2, 3, 5, 8, 13, 21, 34, 55
987
89, 144, 233, 377, 610
1298777728820984005

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

Ответ 7

Оператор return yield позволяет вам возвращать только один элемент за раз. Вы собираете все элементы в списке и снова возвращаете этот список, что является издержками памяти.

Ответ 8

зачем использовать [yield]? Помимо того, что это немного меньше кода, что он делает для меня?

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

Когда действительно светится, когда возвращается только частичный набор. Я считаю, что лучший пример - сортировка. Предположим, у вас есть список объектов, содержащих дату и сумму в долларах с этого года, и вы хотели бы видеть первые несколько (5) записей года.

Чтобы выполнить это, список нужно отсортировать по возрастанию по дате, а затем выполнить первые 5. Если бы это было сделано без урока, весь список нужно было бы отсортировать, вплоть до того, чтобы последние две даты были в порядке.

Однако с выходом, как только первые 5 элементов были установлены, сортировка прекращается и результаты доступны. Это может сэкономить большое количество времени.