Зачем использовать ключевое слово 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 () и предполагая item.Name = = "Джеймс" будет удовлетворен по 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, потому что все, что он сделал, было введено накладные расходы.
когда выход действительно сияет, когда возвращается только частичный набор. Я думаю, что лучший пример-сортировка. Предположим, у вас есть список объектов, содержащих дату и сумма в долларах с этого года, и вы хотели бы видеть первую горсть (5) записей года.
чтобы выполнить это, список должен быть отсортирован по возрастанию по дате, а затем иметь первые 5 взятых. Если это было сделано без дохода весь список должен быть отсортирован, вплоть до того, чтобы убедиться, что последние две даты были в порядке.
однако, с выходом, как только первые 5 элементов были установлены сортировка останавливается и результаты доступный. Это может сэкономить большое количество времени.