为什么使用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关键字的作用。 它告诉编译器构build某种types的东西(一个迭代器)。 但是为什么使用它? 除了less一些代码之外,它对我有什么作用呢?

使用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; } } } 

这将允许你只要你喜欢过滤一个stream,从来没有缓冲超过一个单一的项目。 如果您只需要返回序列中的第一个值,例如,为什么要将所有内容复制到新列表中?

作为另一个例子,你可以使用迭代器块轻松地创build一个无限的stream。 例如,这是一个随机数序列:

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

你将如何在列表中存储无限序列?

我的Edulinq博客系列给出了大量使用迭代器块的LINQ to Objects的示例实现。 LINQ在本质上是懒惰的 – 把事情放在一个列表中根本就不行。

使用“列表”代码,您必须处理完整列表,然后才能将其传递到下一步。 “收益率”版本将处理的项目立即传递到下一步。 如果“下一步”包含“.Take(10)”,那么“良率”版本将只处理前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 ==“James”将在列表中的第二个项目上得到满足时,使用yield的方法将产生两次。 这是一个懒惰的行为。

在使用列表的方法中,将所有n个对象添加到列表中,并将完整列表传递给调用方法。

这正是IEnumerable和IList之间的区别可以突出显示的用例。

我见过的最好的现实世界的例子是计算一个斐波那契数列。

考虑下面的代码:

 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 return语句允许您一次只返回一个项目。 您正在收集列表中的所有项目,并再次返回该列表,这是一个内存开销。

为什么使用[yield]? 除了less一些代码之外,它对我有什么作用呢?

有时它是有用的,有时不是。 如果必须检查和返回整套数据,那么在使用收益率方面没有任何好处,因为它所做的只是开销。

当产量真正发光时,只有一部分集合被返回。 我认为最好的例子是sorting。 假设你有一个包含今年的date和美元数量的对象列表,你希望看到一年中的第一批(5)logging。

为了做到这一点,列表必须按date升序排列,然后取前5个。 如果这样做没有收益, 整个清单将不得不被sorting,直到确保最后两个date是有序的。

然而,有了收益率,一旦前5个项目build立,分拣就停止,结果就可以得到。 这可以节省大量的时间。