Зачем использовать ключевое слово 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 ключевое слово делает. Он говорит компилятору построить определенный тип вещи (итератор). Но зачем им пользоваться? Помимо того, что это немного меньше кода, что он делает для меня?

8 ответов


используя yield коллекция ленивый.

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


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

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);
    }
}

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

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


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

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


можно использовать 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

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


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

когда вышеуказанный код используется для цикла через FilteredList () и предполагая элемент.Name = = "James" будет удовлетворен по 2-му пункту в списке, используя метод yield в два раза доходность. Это ленивое поведение.

где в качестве метода с помощью list добавит все N объектов в список и передаст полный список вызывающему методу.

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


лучший пример реального мира, который я видел для использования 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 и запрашивать только то, что вам нужно.


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

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

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

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

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


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