C#稳固基础:传统遍历与迭代器
Hello,Coders。我们除了天天的码 if…else…之外,还会不断的码出foreach。我今天要说的是:传统遍历需实现的接口及我们还有一种更简洁优雅的方式实现多种迭代器。
传统遍历
传统的遍历即通过让集合类实现IEnumerable、IEnumerator或IEnumerable<T>、IEnumerator<T>接口来支持遍历。
|
1
2
3
4
5
6
7
8
9
10
11
|
public interface IEnumerable // 可枚举接口{ IEnumeratorGetEnumerator();}public interface IEnumerator // 枚举器接口{ object Current { get; } boolMoveNext(); void Reset();} |
- 分析:
1) 从这两个接口的用词选择上,也可以看出其不同:
a) IEnumerable是一个声明式的接口,声明实现该接口的类是可枚举。
b) IEnumerator是一个实现式的接口,IEnumerator对象说明如何实现枚举器。
2) Foreach语句隐式调用集合的无参GetEnumerator方法(不论集合是否有实现IEnumerable接口,但只要有无参GetEnumerator方法并返回IEnumerator就可遍历)。
3) 集合类为什么不直接实现IEnumerable和IEnumerator接口?
这样是为了提高并发性。Eg:一个遍历机制只有一个Current,一旦并发就会出错。然而“将遍历机制与集合分离开来”如果要实现同时遍历同一个集合,只需由集合IEnumerable.GetEnumerator() 返回一个新的包含遍历机制(IEnumerator)的类实例即可。
- 调用过程
插播一段:由foreach执行过程可知其迭代器是延迟计算的。
因为迭代的主体在MoveNext() 中实现,foreach中每次遍历执行到 in 的时候才会调用MoveNext() ,所以其迭代器耗时的指令是延迟计算的。
延迟计算(Lazy evaluation):来源自函数式编程,在函数式编程里,将函数作为参数来传递,传递过程中不会执行函数内部耗时的计算,直到需要这个计算结果的时候才调用,这样就可以因为避免一些不必要的计算而改进性能。 (另外还有linq、DataReader等也运用了延迟计算的思想)
- 具体实现示例
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
|
/// <summary>/// 课程/// </summary>public class Course{ public Course(String name) { this.name = name; } private String name = string.Empty; public String Name { get { return name; } }}public class CourseCollection : IEnumerable<Course>{ public CourseCollection() { arr_Course = new Course[] { new Course("语文"), new Course("数学"), new Course("英语"), new Course("体育") }; } private Course[] arr_Course; public Course this[int index] { get { return arr_Course[index]; } } public int Count { get { return arr_Course.Length; } } public IEnumerator<Course> GetEnumerator() { return new CourseEnumerator(this); } #region 实现 IEnumerable<T> private sealed class CourseEnumerator : IEnumerator<Course> { private readonly CourseCollection courseCollection; private int index; internal CourseEnumerator(CourseCollection courseCollection) { this.courseCollection = courseCollection; index = -1; } public Course Current { get { return courseCollection[index]; } } bool IEnumerator.MoveNext() { index++; return (index < courseCollection.Count); } void IEnumerator.Reset() { index = -1; } …… } #endregion ……} |
有了对“传统遍历”实现方式的理解才能快速明白下一节“迭代器”的实现原理。要知道绝大部分最新的概念其实都可以用最简单的那些概念组合而成。而只有对基本概念理解,才能看清那些复杂概念的实质。
迭代器(iterator)
迭代器是 C# 2.0 中的新功能。它使类或结构支持foreach迭代,而不必“显示”实现IEnumerable或IEnumerator接口。只需要简单的使用 yield 关键字,由 JIT 编译器帮我们编译成实现 IEnumerable或IEnumerator 接口的对象(即:本质还是传统遍历,只是写法上非常简洁)。
对于本节提到的编译后的代码,可通过 Reflector.exe , ILSpy.exe 进行查看。
- 分析
1) yield 语句只能出现在 iterator 块(迭代块)中,该块只能用作方法、运算符或get访问器的主体实现。这类方法、运算符或访问器的“主体”受以下约束的控制:
a) 不允许不安全块。
b) 方法、运算符或访问器的参数不能是 ref 或 out。
2) 迭代器代码使用 yield return 语句依次返回每个元素。yield break 将终止迭代。
a) yield return 的时候会保存当前位置(状态机)并把控制权从迭代器中交给调用的程序,做必要的返回值处理,下一次进入迭代器将从之前保存的位置处开始执行直到迭代结束或调用yield break。
b) yield break 就是控制权交给调用程序就不回来了从而终止迭代。
3) yield return 语句不能放在 try-catch 块中。但可放在后跟 finally 块的 try 块中。
4) yield break 语句可放在 try 块或 catch 块中,但不能放在 finally 块中。
5) yield 语句不能出现在匿名方法中。
6) 迭代器必须返回相同类型的值,因为最后输出为IEnumerator.Current是单一类型。(见下面示例)
7) 在同一个迭代器中可以使用多个 yield 语句。(见下面示例)
8) 自定义迭代器:迭代器可以自定义名称、可以带参数,但在foreach中需要显示去调用自定义的迭代器。(见下面示例)
9) 迭代器的返回类型必须为IEnumerator、IEnumerator<T>或IEnumerable、IEnumerable<T>。(见下面示例)
- 迭代器的具体实现
1) 返回类型为IEnumerator、IEnumerator<T>
返回此类型的迭代器方法必须满足:
a) 必须有GetEnumerator且不带参数;
b) 必须是public公共成员;
见示例代码,我们将CourseCollection集合对象的IEnumerable.GetEnumerator() 方法实现如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
// 注意:返回什么,泛型就为什么类型public IEnumerator<String> GetEnumerator(){ for (int i = 0; i < arr_Course.Length; i++) { Course course = arr_Course[i]; yield return "选修:" + course.Name; // 两个 yield return yield return Environment.NewLine; // 每个yield return 保存当前位置并返回值,下一次进入迭代器时将从之前保存的位置处开始执行 if (String.Compare(course.Name, "体育") == 0) yield break; List<string> strs=new List<string>{"435435","546546"}; foreach (string s in strs) { Console.WriteLine(s); } }} |
经过 JIT 编译后,会自动生成一个实现了 IEnumerator<String> 接口的对象。具体代码可通过 Reflector 工具查看,下面展示其中的MoveNext() 代码:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
private bool MoveNext(){ switch (this.<>1__state) { case 0: this.<>1__state = -1; this.<i>5__2 = 0; while (this.<i>5__2 < this.<>4__this.arr_Course.Length) { this.<course>5__3 = this.<>4__this.arr_Course[this.<i>5__2]; this.<>2__current = "选修:" + this.<course>5__3.Name; this.<>1__state = 1; return true; Label_007C: this.<>1__state = -1; this.<>2__current = Environment.NewLine; this.<>1__state = 2; return true; Label_009C: this.<>1__state = -1; if (string.Compare(this.<course>5__3.Name, "体育") == 0) { break; } this.<>g__initLocal0 = new List<string>(); this.<>g__initLocal0.Add("435435"); this.<>g__initLocal0.Add("546546"); this.<strs>5__4 = this.<>g__initLocal0; foreach (string s in this.<strs>5__4) { Console.WriteLine(s); } this.<i>5__2++; } break; case 1: goto Label_007C; case 2: goto Label_009C; } return false;} |
通过代码,我们可以知道:
a) 同一个迭代器中有多少个 yield return语句,while 循环中就有多少个 return true 。
b) yield retuen结束本次循环,yield break结束整个循环。输出数据的顺序通过生成类中的一个state状态字段做为switch 标识来决定要输出第几个 yield return 。yield return在每个case里面改变state内部字段,使正确执行完多个return返回数据,并最后通过return true来结束本次MoveNext()。而yield break语句直接生成break并重置state状态字段为switch中没有的值而跳出switch语句,通过执行最后的return false来结束整个循环。
c) 注意:yield return 后面的 List<string>代码段也会被执行。
2) 返回类型为IEnumerable、IEnumerable<T>
返回此类型的迭代器必须满足:
a) 必须可以在foreach语句中被调用(访问权限);
返回此类型的迭代器通常用于实现自定义迭代器,即:迭代器可以自定义名称、可以带参数。Eg:(升序和降序)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public IEnumerable<String> GetEnumerable_ASC(){ return this;}public IEnumerable<String> GetEnumerable_DESC(){ for (inti = arr_Course.Length - 1; i>= 0; i--) { Course course = arr_Course[i]; yield return "选修:" +course.Name; yield return Environment.NewLine; }} |
需如下进行迭代器调用:
|
1
2
3
|
yield_Example.CourseCollection col2 = new yield_Example.CourseCollection();foreach (String str in col2.GetEnumerable_ASC()){ // col2.GetEnumerable_ASC()}foreach (String str in col2.GetEnumerable_DESC()){//col2.GetEnumerable_DESC()} |
经过 JIT 编译后,会自动生成一个直接实现IEnumerator<String>和IEnumerator<String>接口的对象,其GetEnumerator() 方法返回自己this(因为本身实现了IEnumerator接口)。
这是因为在不同foreach遍历中所访问的由编译器自动生成的迭代器具有其自己独立的状态,所以迭代器之间互不影响,不存在并发的问题。
OVER,谢谢观看。(如有缺漏或错误请留言,谢谢!!!)
C#稳固基础:传统遍历与迭代器的更多相关文章
- python基础(8)--迭代器、生成器、装饰器
1.迭代器 迭代器是访问集合元素的一种方式.迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束.迭代器只能往前不会后退,不过这也没什么,因为人们很少在迭代途中往后退.另外,迭代器的一大优 ...
- Java基础知识强化之集合框架笔记07:Collection集合的遍历之迭代器遍历
1. Collection的迭代器: Iterator iterator():迭代器,集合的专用遍历方式 2. 代码示例: package cn.itcast_03; import java.util ...
- 【笨木头Lua专栏】基础补充04:迭代器初探
今天学习的内容还蛮有意思的,让我兴奋了一下~ 笨木头花心贡献,哈?花心?不,是用心~ 转载请注明,原文地址: http://www.benmutou.com/archives/1714 文章来源:笨木 ...
- java 15-4 集合的专用遍历工具 迭代器
Iterator iterator():迭代器,集合的专用遍历方式 A:Object next():获取元素,并移动到下一个位置. 有时候会出现这样的错误: NoSuchElementExceptio ...
- Python开发【第一篇】Python基础之生成器和迭代器
生成器和迭代器 1.生成器 一个函数调用时返回一个迭代器,那这个函数就叫做生成器(generator):如果函数中包含yield语法,那这个函数就会变成生成器: def func(): yield 1 ...
- Python-Day4 Python基础进阶之生成器/迭代器/装饰器/Json & pickle 数据序列化
一.生成器 通过列表生成式,我们可以直接创建一个列表.但是,受到内存限制,列表容量肯定是有限的.而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面 ...
- python基础(八)-迭代器与生成器
一.迭代器 li=[1,2,3] f=li.__iter__() print(f) print(f.__next__()) print(f.__next__()) print(f.__next__() ...
- Java之集合的遍历与迭代器
集合的遍历 依次获取集合中的每一个元素 将集合转换成数组,遍历数组 //取出所有的学号, 迭代之后显示学号为1004-1009 Object[] c=map.keySet().toArray();// ...
- python基础---列表生成器、迭代器等
一.列表生成式 用来创建list的表达式,相当于for循环的简写形式 语法: [表达式 for循环 判断条件] ''' 普通写法 ''' def test(): l= [] for i in rang ...
随机推荐
- SharePoint暂时禁用事件触发
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsof ...
- iOS---用Application Loader 上传的时候报错No suitable application records were found. Verify your bundle identifier 'xx' is correct
用Application Loader 上传的时候报错,突然发现用Application Loader的账号 竟然不是公司的账号 换成公司的账号 就可以了.
- 基于Ruby的watir-webdriver自动化测试方案与实施(五)
接着基于Ruby的watir-webdriver自动化测试方案与实施(四) http://www.cnblogs.com/Javame/p/4164570.html 继续 ... ... 关于特殊控件 ...
- Spring 中classPath:用法
参考文章地址: http://hi.baidu.com/huahua035/item/ac8a27a994b55bad29ce9d39 http://blog.csdn.net/lushuaiyin/ ...
- TNS-12541: TNS:no listener TNS-12560 TNS-00511: No listener
为了测试需要,系统管理员帮忙将一台ORACLE数据库服务器克隆到虚拟机上,我上去删除了root.oracle.tomcat账号下的crontab定时作业,然后启动了ORACLE数据库实例,删除 ...
- Python基础2
入门知识拾遗 一.作用域 对于变量的作用域,执行声明并在内存中存在,该变量就可以在下面的代码中使用. if 1==1: name = 'yuxiaozheng' print name 外层变量,可以被 ...
- MSDB数据库置疑的解决方法
1.原因 机房停电,服务器非法关机,导致MSDB数据库被“置疑” 2.Msdb数据库的作用 Msdb 数据库供SQLServer 代理程序调度警报和作业以及记录操作员时使用.比如,我们备份了一个数据库 ...
- Asp.Net MVC+BootStrap+EF6.0实现简单的用户角色权限管理4
首先先加个区域,名为Admin using System.Web.Mvc; namespace AuthorDesign.Web.Areas.Admin { public class AdminAre ...
- css工具收集
收集一些css的生成工具,开发中可以直接拿过来用.特别是那些css3中的一些新的特性. 1 css渐变背景在线生成工具 http://www.colorzilla.com/gradient ...
- 帆软报表FineReport中数据连接之Weblogic配置JNDI连接
1. 制作报表的原理 在帆软报表FineReport设计器中先用JDBC连接到数据库,建立数据库连接,然后用SQL或者其他方法创建数据集,使用数据集制作报表,然后把建立的数据库连接从JDBC连接改成J ...