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 2010 GridView/SPGridView完全应用系统样式
自定义开发页面如果用到了GridView或SPGridView默认跟列表的样式是不一样的,如要要一样,需要: 1)aspx <asp:GridView DataKeyNames="ID ...
- “#if 0/#if 1 ... #endif”的作用
1. "#if 0/#if 1 ... #endif"的作用,我们知道,C标准不提供C++里的"//"这样的单行风格注释而只提供"/* */" ...
- 12-返回指针的函数&&指向函数的指针
前言 接下来我只讲指针的最常见用法,比如这一章的内容----返回指针的函数 与 指向函数的指针 一.返回指针的函数 指针也是C语言中的一种数据类型,因此一个函数的返回值肯定可以是指针类型的. 返回 ...
- DP优化与换零钱问题
1 当贪心不再起效的时候 对于换零钱问题,最简单也是性能最好的方法就是贪心算法.可是贪心算法一定要满足面值相邻两个零钱至少为二倍关系的前提条件.例如1,2,5,10,20……这样的零钱组应用贪心最简单 ...
- c++ 奇特的递归模板模式(CRTP)
概述 使用派生类作为模板参数特化基类. 与多态的区别 多态是动态绑定(运行时绑定),CRTP是静态绑定(编译时绑定) 在实现多态时,需要重写虚函数,因而这是运行时绑定的操作. CRTP在编译期确定通过 ...
- SQL Server Integration Services(SSIS) 包配置与部署
SSIS配置此处的配置方式,主要针对到正式服务器上要修改服务器名,和连接服务器等配置注意:1. 包配置在windows2008上生成后,在windows2003上mysql的配置无法使用,总是报错连接 ...
- Django入门
Django文档: https://docs.djangoproject.com/en/1.10/ref/ 一.简单创建app 1.1 命令行创建project和app. django-admin s ...
- MySQL SQL 注入
如果您通过网页获取用户输入的数据并将其插入一个MySQL数据库,那么就有可能发生SQL注入安全的问题. 本博文将为大家介绍如何防止SQL注入,并通过脚本来过滤SQL中注入的字符. 所谓SQL注入,就是 ...
- ANSYS17.0详细安装图文教程
ANSYS 17.0是ANSYS的最新版.下面以图文方式详细描述该软件的安装过程. 1 安装前的准备 安装之前需要做的准备工作: 在硬盘上腾出30G的空间来.(视安装模块的多少,完全安装可能需要二十多 ...
- Apache、nginx配置的网站127.0.0.1可以正常访问,内外网的ip地址无法访问,谁的锅?
最近做开发,发现一个比较尴尬的问题.因为我是一个web开发者,经常要用到Apache或者nginx等服务器软件,经过我测试发现,只要我打开了adsafe,我便不能通过ip地址访问我本地的网站了,比如我 ...