你们好,这是 [C#.NET 拾遗补漏] 系列的第 07 篇文章。数组
在 C# 中,大多数方法都是经过 return 语句当即把程序的控制权交回给调用者,同时也会把方法内的本地资源释放掉。而包含 yield 语句的方法则容许在依次返回多个值给调用者的期间保留本地资源,等全部值都返回结束时再释放掉原本资源,这些返回的值造成一组序列被调用者使用。在 C# 中,这种包含 yield 语句的方法、属性或索引器就是迭代器。bash
迭代器中的 yield 语句分为两种:app
yeild return
,把程序控制权交回调用者并保留本地状态,调用者拿到返回的值继续日后执行。yeild break
,用于告诉程序当前序列已经结束,至关于正常代码块的 return 语句(迭代器中直接使用 return 是非法的)。下面是一个用来生成斐波纳契序列的迭代器示例:ui
IEnumerable<int> Fibonacci(int count) { int prev = 1; int curr = 1; for (int i = 0; i < count; i++) { yield return prev; int temp = prev + curr; prev = curr; curr = temp; } } void Main() { foreach (int term in Fibonacci(10)) { Console.WriteLine(term); } }
输出:code
1 1 2 3 5 8 13 21 34 55
实际场景中,咱们通常不多直接写迭代器,由于大部分须要迭代的场景都是数组、集合和列表,而这些类型内部已经封装好了所需的迭代器。好比 C# 中的数组之因此能够被遍历是由于它实现了 IEnumerable
接口,经过 GetEnumerator()
方法能够得到数组的列举器 Enumerator,而该列举器就是经过迭代器来实现的。好比最多见的一种使用场景就是遍历数组中的每个元素,以下面逐个打印数组元素的示例。对象
int[] numbers = { 1, 2, 3, 4, 5 }; IEnumerator enumerator = numbers.GetEnumerator(); while (enumerator.MoveNext()) { Console.WriteLine(enumerator.Current); }
其实这就是 foreach 的工做原理,上面代码能够用 foreach 改写以下:索引
int[] numbers = { 1, 2, 3, 4, 5 }; foreach (int number in numbers) { Console.WriteLine(number); }
固然,列举器不必定非要经过迭代器实现,例以下面这个自定义的列举器 CoffeeEnumerator。接口
public class CoffeeCollection : IEnumerable { private CoffeeEnumerator enumerator; public CoffeeCollection() { enumerator = new CoffeeEnumerator(); } public IEnumerator GetEnumerator() { return enumerator; } public class CoffeeEnumerator : IEnumerator { string[] items = new string[3] { "espresso", "macchiato", "latte" }; int currentIndex = -1; public object Current { get { return items[currentIndex]; } } public bool MoveNext() { currentIndex++; if (currentIndex < items.Length) { return true; } return false; } public void Reset() { currentIndex = 0; } } }
使用:ci
public static void Main(string[] args) { foreach (var coffee in new CoffeeCollection()) { Console.WriteLine(coffee); } }
理解迭代器和列举器能够帮助咱们写出更高效的代码。好比判断一个 IEnumerable<T>
对象是否包含元素,常常看到有些人这么写:资源
if(enumerable.Count() > 0) { // 集合中有元素 }
但若是用列举器的思惟稍微思考一下就知道,Count()
为了得到集合元素数量必然要迭代完全部元素,时间复杂度为 O(n)。而仅仅是要知道集合中是否包含元素,其实迭代一次就能够了。因此效率更好的作法是:
if(enumerable.GetEnumerator().MoveNext()) { // 集合中有元素 }
这样写时间复杂度是 O(1),效率显然更高。为了书写方便,C# 提供了扩展方法 Any()
。
if(enumerable.Any()) { // 集合中有元素 }
因此若有须要,应尽量使用 Any 方法,效率更高。
再好比在 EF Core 中,须要执行 IQueryable<T>
查询时,有时候使用 AsEnumerable()
比使用 ToList、ToArray 等更高效,由于 ToList、ToArray 等会当即执行列举操做,而 AsEnumerable()
能够把列举操做延迟到真正被须要的时候再执行。固然也要考虑实际应用场景,Array、List 等更方便调用者使用,特别是要获取元素总数量、增删元素等这种操做。