C#2.0新增功能05 迭代器
迭代器可用于逐步迭代集合,例如列表和数组。
迭代器方法或 get
访问器可对集合执行自定义迭代。 迭代器方法使用 yield return 语句返回元素,每次返回一个。 到达 yield return
语句时,会记住当前在代码中的位置。 下次调用迭代器函数时,将从该位置重新开始执行。
通过 foreach 语句或 LINQ 查询从客户端代码中使用迭代器。
在以下示例中,foreach
循环的首次迭代导致 SomeNumbers
迭代器方法继续执行,直至到达第一个 yield return
语句。 此迭代返回的值为 3,并保留当前在迭代器方法中的位置。 在循环的下次迭代中,迭代器方法的执行将从其暂停的位置继续,直至到达 yield return
语句后才会停止。 此迭代返回的值为 5,并再次保留当前在迭代器方法中的位置。 到达迭代器方法的结尾时,循环便已完成。
static void Main() { foreach (int number in SomeNumbers()) { Console.Write(number.ToString() + " "); } // 输出: 3 5 8 Console.ReadKey(); } public static System.Collections.IEnumerable SomeNumbers() { yield return 3; yield return 5; yield return 8; }
迭代器方法或 get
访问器的返回类型可以是 IEnumerable、IEnumerable<T>、IEnumerator 或 IEnumerator<T>。
可以使用 yield break
语句来终止迭代。
对于本主题中除简单迭代器示例以外的所有示例,请为 System.Collections 和 System.Collections.Generic 命名空间加入 using 指令。
yield return
语句。 在 Main
中,foreach
语句体的每次迭代都会创建一个对迭代器函数的调用,并将继续到下一个 yield return
语句。static void Main() { foreach (int number in EvenSequence(5, 18)) { Console.Write(number.ToString() + " "); } // 输出: 6 8 10 12 14 16 18 Console.ReadKey(); } public static System.Collections.Generic.IEnumerable<int> EvenSequence(int firstNumber, int lastNumber) { // 迭代集合中的偶数. for (int number = firstNumber; number <= lastNumber; number++) { if (number % 2 == 0) { yield return number; } } }
在以下示例中,DaysOfTheWeek
类实现 IEnumerable 接口,此操作需要 GetEnumerator 方法。 编译器隐式调用 GetEnumerator
方法,此方法返回 IEnumerator。
GetEnumerator
方法通过使用 yield return
语句每次返回 1 个字符串。
static void Main() { DaysOfTheWeek days = new DaysOfTheWeek(); foreach (string day in days) { Console.Write(day + " "); } // 输出: Sun Mon Tue Wed Thu Fri Sat Console.ReadKey(); } public class DaysOfTheWeek : IEnumerable { private string[] days = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; public IEnumerator GetEnumerator() { for (int index = 0; index < days.Length; index++) { // 迭代每一天 yield return days[index]; } } }
下例创建了一个包含动物集合的 Zoo
类。
引用类实例 (theZoo
) 的 foreach
语句隐式调用 GetEnumerator
方法。 引用 Birds
和 Mammals
属性的 foreach
语句使用 AnimalsForType
命名迭代器方法。
1 static void Main() 2 { 3 Zoo theZoo = new Zoo(); 4 5 theZoo.AddMammal("Whale"); 6 theZoo.AddMammal("Rhinoceros"); 7 theZoo.AddBird("Penguin"); 8 theZoo.AddBird("Warbler"); 9 10 foreach (string name in theZoo) 11 { 12 Console.Write(name + " "); 13 } 14 Console.WriteLine(); 15 // 输出: Whale Rhinoceros Penguin Warbler 16 17 foreach (string name in theZoo.Birds) 18 { 19 Console.Write(name + " "); 20 } 21 Console.WriteLine(); 22 // 输出: Penguin Warbler 23 24 foreach (string name in theZoo.Mammals) 25 { 26 Console.Write(name + " "); 27 } 28 Console.WriteLine(); 29 // 输出: Whale Rhinoceros 30 31 Console.ReadKey(); 32 } 33 34 public class Zoo : IEnumerable 35 { 36 // 私有成员 37 private List<Animal> animals = new List<Animal>(); 38 39 // 公共方法 40 public void AddMammal(string name) 41 { 42 animals.Add(new Animal { Name = name, Type = Animal.TypeEnum.Mammal }); 43 } 44 45 public void AddBird(string name) 46 { 47 animals.Add(new Animal { Name = name, Type = Animal.TypeEnum.Bird }); 48 } 49 50 public IEnumerator GetEnumerator() 51 { 52 foreach (Animal theAnimal in animals) 53 { 54 yield return theAnimal.Name; 55 } 56 } 57 58 // 公共成员 59 public IEnumerable Mammals 60 { 61 get { return AnimalsForType(Animal.TypeEnum.Mammal); } 62 } 63 64 public IEnumerable Birds 65 { 66 get { return AnimalsForType(Animal.TypeEnum.Bird); } 67 } 68 69 // 私有方法 70 private IEnumerable AnimalsForType(Animal.TypeEnum type) 71 { 72 foreach (Animal theAnimal in animals) 73 { 74 if (theAnimal.Type == type) 75 { 76 yield return theAnimal.Name; 77 } 78 } 79 } 80 81 // 私有类 82 private class Animal 83 { 84 public enum TypeEnum { Bird, Mammal } 85 86 public string Name { get; set; } 87 public TypeEnum Type { get; set; } 88 } 89 }
在以下示例中,Stack<T> 泛型类实现 IEnumerable<T> 泛型接口。 Push 方法将值分配给类型为 T
的数组。 GetEnumerator 方法通过使用 yield return
语句返回数组值。
除了泛型 GetEnumerator 方法,还必须实现非泛型 GetEnumerator 方法。 这是因为从 IEnumerable 继承了 IEnumerable<T>。 非泛型实现遵从泛型实现的规则。
本示例使用命名迭代器来支持通过各种方法循环访问同一数据集合。 这些命名迭代器为 TopToBottom
和 BottomToTop
属性,以及 TopN
方法。
BottomToTop
属性在 get
访问器中使用迭代器。
1 static void Main() 2 { 3 Stack<int> theStack = new Stack<int>(); 4 5 // 向堆栈中添加项 6 for (int number = 0; number <= 9; number++) 7 { 8 theStack.Push(number); 9 } 10 11 // 从堆栈中检索项。 12 // 此处允许使用 foreach,因为 foreach 实现了 IEnumerable<int> 13 foreach (int number in theStack) 14 { 15 Console.Write("{0} ", number); 16 } 17 Console.WriteLine(); 18 // 输出: 9 8 7 6 5 4 3 2 1 0 19 20 // 此处允许使用 foreach,因为 theStack.TopToBottom 属性返回了 IEnumerable(Of Integer). 21 foreach (int number in theStack.TopToBottom) 22 { 23 Console.Write("{0} ", number); 24 } 25 Console.WriteLine(); 26 // 输出: 9 8 7 6 5 4 3 2 1 0 27 28 foreach (int number in theStack.BottomToTop) 29 { 30 Console.Write("{0} ", number); 31 } 32 Console.WriteLine(); 33 // 输出: 0 1 2 3 4 5 6 7 8 9 34 35 foreach (int number in theStack.TopN(7)) 36 { 37 Console.Write("{0} ", number); 38 } 39 Console.WriteLine(); 40 // 输出: 9 8 7 6 5 4 3 41 42 Console.ReadKey(); 43 } 44 45 public class Stack<T> : IEnumerable<T> 46 { 47 private T[] values = new T[100]; 48 private int top = 0; 49 50 public void Push(T t) 51 { 52 values[top] = t; 53 top++; 54 } 55 public T Pop() 56 { 57 top--; 58 return values[top]; 59 } 60 61 // 此方法实现了GetEnumerator()方法. 它允许在 foreach 语句中使用类的实例。 63 public IEnumerator<T> GetEnumerator() 64 { 65 for (int index = top - 1; index >= 0; index--) 66 { 67 yield return values[index]; 68 } 69 } 70 71 IEnumerator IEnumerable.GetEnumerator() 72 { 73 return GetEnumerator(); 74 } 75 76 public IEnumerable<T> TopToBottom 77 { 78 get { return this; } 79 } 80 81 public IEnumerable<T> BottomToTop 82 { 83 get 84 { 85 for (int index = 0; index <= top - 1; index++) 86 { 87 yield return values[index]; 88 } 89 } 90 } 91 92 public IEnumerable<T> TopN(int itemsFromTop) 93 { 94 // 如有必要,返回少于 itemsFromTop 95 int startIndex = itemsFromTop >= top ? 0 : top - itemsFromTop; 96 97 for (int index = top - 1; index >= startIndex; index--) 98 { 99 yield return values[index]; 100 } 101 } 102 103 }
迭代器可用作一种方法,或一个 get
访问器。 不能在事件、实例构造函数、静态构造函数或静态终结器中使用迭代器。
必须存在从 yield return
语句中的表达式类型到迭代器返回的 IEnumerable<T> 类型参数的隐式转换。
在 C# 中,迭代器方法不能有任何 in
、ref
或 out
参数。
在 C# 中,“yield”不是保留字,只有在 return
或 break
关键字之前使用时才有特殊含义。
即使将迭代器编写成方法,编译器也会将其转换为实际上是状态机的嵌套类。 只要客户端代码中的 foreach
循环继续,此类就会跟踪迭代器的位置。
若要查看编译器执行的操作,可使用 Ildasm.exe 工具查看为迭代器方法生成的 Microsoft 中间语言代码。
为类或结构创建迭代器时,不必实现整个 IEnumerator 接口。 编译器检测到迭代器时,会自动生成 IEnumerator 或 IEnumerator<T> 接口的 Current
、MoveNext
和 Dispose
方法。
在 foreach
循环(或对 IEnumerator.MoveNext
的直接调用)的每次后续迭代中,下一个迭代器代码体都会在上一个 yield return
语句之后恢复。 然后继续下一个 yield return
语句,直至到达迭代器体的结尾,或直至遇到 yield break
语句。
迭代器不支持 IEnumerator.Reset 方法。 若要从头开始重新迭代,必须获取新的迭代器。 在迭代器方法返回的迭代器上调用 Reset 会引发 NotSupportedException。
有关其他信息,请参阅 C# 语言规范。
需要使用复杂代码填充列表序列时,使用迭代器可保持 foreach
循环的简单性。 需执行以下操作时,这可能很有用:
-
在第一次
foreach
循环迭代之后,修改列表序列。 -
避免在
foreach
循环的第一次迭代之前完全加载大型列表。 一个示例是用于加载一批表格行的分页提取。 另一个示例关于 EnumerateFiles 方法,该方法在 .NET Framework 中实现迭代器。 -
在迭代器中封装生成列表。 使用迭代器方法,可生成该列表,然后在循环中产出每个结果。
成在管理,败在经验;嬴在选择,输在不学! 贵在坚持!
个人作品
BIMFace.SDK.NET
开源地址:https://gitee.com/NAlps/BIMFace.SDK
系列博客:https://www.cnblogs.com/SavionZhang/p/11424431.html
系列视频:https://www.cnblogs.com/SavionZhang/p/14258393.html
技术栈
1、Visual Studio、.NET Core/.NET、MVC、Web API、RESTful API、gRPC、SignalR、Java、Python
2、jQuery、Vue.js、Bootstrap、ElementUI
3、数据库:分库分表、读写分离、SQLServer、MySQL、PostgreSQL、Redis、MongoDB、ElasticSearch、达梦DM
4、架构:DDD、ABP、SpringBoot、jFinal
5、环境:跨平台、Windows、Linux、Nginx
6、移动App:Android、IOS、HarmonyOS、微信小程序、钉钉、uni-app、MAUI
分布式、高并发、云原生、微服务、Docker、CI/CD、DevOps、K8S;Dapr、RabbitMQ、Kafka、RPC、Elasticsearch。
欢迎关注作者头条号 张传宁IT讲堂,获取更多IT文章、视频等优质内容。
出处:www.cnblogs.com/SavionZhang
作者:张传宁 技术顾问、培训讲师、微软MCP、系统架构设计师、系统集成项目管理工程师、科技部创新工程师。
专注于企业级通用开发平台、工作流引擎、自动化项目(代码)生成器、SOA 、DDD、 云原生(Docker、微服务、DevOps、CI/CD);PDF、CAD、BIM 审图等研究与应用。
多次参与电子政务、图书教育、生产制造等企业级大型项目研发与管理工作。
熟悉中小企业软件开发过程:可行调研、需求分析、架构设计、编码测试、实施部署、项目管理。通过技术与管理帮助中小企业实现互联网转型升级全流程解决方案。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
如有问题,可以通过邮件905442693@qq.com联系。共同交流、互相学习。
如果您觉得文章对您有帮助,请点击文章右下角【推荐】。您的鼓励是作者持续创作的最大动力!