讲解
在 C# 基础库中经常可以看到很多方法返回值是 IEnumerable 类型,那么为什么返回 IEnumerable 而不是返回 IList、ICollection 或 List 类型呢?看完这篇文章你就能得到答案了。IEnumerable 它表示该集合中的元素可以被遍历,一般来说 IEnumerable 类型的对象会和 yield 紧密结合和。在 C# 中大部分方法是通过 return 语句把运行果返给调用者,同时把控制权也交回给了调用者。下面的代码将打印斐波拉契数据:
IEnumerable<int> fibonaccis= Fibonacci(10); foreach (var f in fibonaccis) { Console.Write("{0} ", f); } //计算斐波拉契数据 List<int> Fibonacci(int count) { int p= 1; int c= 1; List<int> result = new List<int>(); for (int i = 0; i < count; i++) { result.Add(p); int temp = p+ c; p= c; c= temp; } return result; }
当我们在Console.Write() 打印结果之前,变量 fibonaccis计算出所有的数据了,运行代码后会把结果马上输出,这样的代码表面上看着没什么问题。那么我们换一个场景来想想,假设Fibonacci()方法内部每次计算得到下一个数都需要耗费较长的时间会出现什么情况,下面我们就来模拟所需的耗时,Fibonacci方法修改后的代码如下:
for (int i = 0; i < count; i++) { result.Add(p); Thread.Sleep(3000); int temp = p + c; p = c; c= temp; } return result; }
我们再次运行,在大概等待了30秒后马山就打印了数字。但是在等待的这段时间里我们没办法了解到程序运算的进展,运行过程中没有任何反馈的。如果要解决这个问题,我们可以通过 yield 关键字。yield 它可以把每一步的计算都推迟到程序实际需要的时候再执行,也就是说我们不用等所有结果都运行完才执行后续代码。下面的代码是我们使用 yield 关键字改造的 Fibonacci() 方法:
IEnumerable<int> Fibonacci(int count) { int p= 1; int c= 1; for (int i = 0; i < count; i++) { yield return p; Thread.Sleep(3000); int temp = p + c; p = c; c = temp; } }
运行代码后,会每隔 3 秒会输出一个数字直到输出所有数字。虽然说总等待时间是一样的,但对于部分程序来说这样总比让用户一直等着强。
总结
yield 关键字的用途是把指令的执行推迟到程序实际需要的时候,它可以使得我们更细致地控制集合每个元素产生的时机。好处是可以像上面演示的那样尽可能即时地给用户响应。还有一个好处是可以提高内存使用效率。通过 yield 返回的 IEnumerable 类型表示这是一个可以被遍历的数据集合。它之所以可以被遍历是因为它实现了一个标准的 IEnumerable 接口。我们把像上面这种包含 yield 语句并返回 IEnumerable 类型的方法称为迭代器(Iterator)。
tip:包含 yield 语句的方法的返回类型也可以是 IEnumerator<T>,它比迭代器更低一个层级,迭代器是列举器的一种实现。
迭代器方法和普通的方法相比,普通方法是通过 return 语句立即把程序的控制权交回给调用者,也把方法内的局部资源释放掉。迭代器方法则是依次返回多个值给调用者,并在这期间保留局部资源,等所有值都返回结束时再释放掉局部资源,这些返回的值将形成一组序列被调用者使用。
迭代器可以用于方法、属性或索引器中。迭代器中的 yield 语句分为两种:
yeild return,把程序控制权交回调用者并保留本地状态,调用者拿到返回的值继续往后执行。
yeild break,用于告诉程序当前序列已经结束,相当于正常代码块的 return 语句(迭代器中直接使用 return 是非法的)。
TIP:实际场景中一般很少写迭代器,因为大部分需要迭代的场景都是数组、集合和列表,这些类型内部已经封装好了所需的迭代器。