编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

C#核心-迭代器揭秘1(c语言 迭代器)

wxchong 2024-09-30 04:25:19 开源技术 201 ℃ 0 评论

迭代器,我想大家都听说过,但是我想这块内容很容易让人忽视,或许你每天都在用,但是你不自知。我想你肯定知道什么是foreach,因为这个你每天都在用。你肯定看到过IEnumerable,IEnumerator这两个接口,看到过关键字yield,但是或许你只是在一些微软提供的数据结构里面看到过,你自己并没有去实现过。

迭代器是一种设计模式,并不是语言层面的。我想说的意思是迭代器不局限编程语言,也就是他不是C#专有,其他很多语言都可以实现。他的目的是"构建出一个数据管道,把源头数据通过一系列的转换和过滤变成你想要的数据",为什么我要用引号,因为这句话并不好理解。首先在C#里面,只要实现了IEnumerable的类就是实现了迭代器设计模式的类,就能够在foreach里面遍历。反过来说,能放在foreach遍历的对象都是实现了IEnumerable的迭代器类,而IEnumerable实现类里面利用了IEnumerator这个实现体,所以IEnumerable 和 IEnumerator是密不可分的。但是C#里面一定是只能实现了IEnumerable,IEnumerator才是实现了迭代器吗,当然不是,我说了迭代器是设计模式,不局限于C#,js都可以。

接下来我通过几个例子说明怎么使用,请看下图。



不忘初心,方得始终,我接下来举的所有例子的目的都是在说明迭代器的作用,就是那句话。"构建出一个数据管道,把源头数据通过一系列的转换和过滤变成你想要的数据"。

请看上图3,4,我想要把"a","b","c","d","e"这个数组按照我想要的顺序显示出来,比如按照"c"开头的,"c","d","e","a","b",前者的数据是"源头数据",后者是"你想要的数据"。我构建了两个类

InterationSample,InterationSampleIterator,

前者实现了IEnumerable,后者实现了IEnumerator。我们先看看这两个接口定义的方法,请看图1,图2。 两个接口都很简单,

IEnumerable定义了一个方法GetEnumerator,返回IEnumerator接口.。

IEnumerator接口定义了两个方法和一个属性,我们看这两个实现类。

InterationSample实现了GetEnumerator,返回的是IEnumerator接口,

也就是IterationSampleIterator这个类,

InterationSample构造函数两个参数,一个是原始数据数组,

另外一个是输出数据的起始标记位。

IterationSample很简单,foreach循环开始,

首先会调用GetEnumerator方法获取IEnumerator对象,然后执行MoveNext方法,判断是否需要继续执行下去。

"position++"之后position等于4,

"var ret = position <= parent.startingPoint + parent.values.Length;"

"parent.startingPoint"是3,

"parent.values.Length"是5,

那么4<8成立,说明迭代器继续。为什么这么判断,我们先看后面的代码。

当然MoveNext方法返回true,那么foreach就会取Current这个属性的值。

可以看出来,"index=position",等于4,然后

"index=(index-2)%parent.values.Length",

"index=(4-2)%5",

取余数等结果等于2。

然后返回"parent.values[index]"也就是第三个数据"c"。看结果图5,显示出来了"c",

也就是Current的属性值给了foreach里面变量x,所以显示出来"c"。

这个也是我们预期的,因为我们正想从"c"开始输出。接着继续执行MoveNext方法,

"position++"之后position等于5,

"var ret = position <= parent.startingPoint + parent.values.Length;"

"parent.startingPoint"是3,

"parent.values.Length"是5,

那么5<=8成立。

说明迭代器继续,然后调用Current get属性,

"index=(index-2)%parent.values.Length",

"index=(5-2)%5",

取余数等结果等于3,

然后返回"parent.values[index]"也就是第三个数据"d"。

因为Current获取数组的数据是按照索引来的,所以通过取余方式获取索引比较合适,这也 说明为什么MoveNext方法要用

"var ret = position <= parent.startingPoint + parent.values.Length;"

这句话进行判断了。大家想一下这个逻辑就知道了。

大家可能对于foreach怎么运行代码搞不清楚,才导致理解上面的例子需要有点时间。那么我换另外一种方式遍历迭代器。请看下图。

这个代码就清晰多了,而且和我们上面描述的过程是一样的,结果也是一样的。我想这个代码非常清楚就不再描述了。

看到这里你有没有感到疑问?要实现从"a","b","c","d","e"到"c","d","e","a","b"的转换,或者"d","e","a","b","c",我tm需要搞得这么复杂么,我使用for循环也就几行代码就搞定了。我想和你说是的,我们确实第一念头就是直接通过for循环,写的代码也少。但是我想和你说的是,这里我只是想要循序渐进地往下说,慢慢你就明白用迭代器的好处在哪里了。我们回过头再看看这个代码。

确实代码有点多,现在我要引入yield关键字了。请看下图。


先说明一下,foreach C#1.0就有了,也就是上面的代码C#1.0大体就能运行。yield是C#2.0才引入的。请看下面的C#各个版本升级的内容


让你明白yield关键字的作用,我还得和你说另外一个话题,就是C#代码执行的过程,这里我想长话短说。首先C#编译器会将C#代码编译成IL(中间语言),元数据。dll大家都知道,他里面大体就是这两样东西。我想只介绍IL语言,因为这个话题应该另外起一个文章介绍才行。

IL到底是个啥,有什么用呢?原因就是因为.NET平台不止C#一种语言,还有vb,f#等很多种语言,你开心的话你也可以搞一个语言出来。那么这些语言语法会有不同,微软就想要通过一个中间语言统一这些语言,让这些语言通过各自编译器生成一样的代码,也就是IL中间语言。那么后面这些代码就统一处理了。也就是微软说我支持你发明一个新语言,我不管你发明的语言是中文还是汉语我都不在乎,只要你的编译器生成的代码是IL代码,那么就可以在我.NET平台运行。执行的过程会用到JIT编译器,会将IL代码生成机器语言,也就是汇编语言,这样cpu就可以执行了。

为什么我要扯开话题聊一下C#代码执行过程,就是因为在我们学习过程中会发现C#一直在升级,目前.NET6 已经到了C# 10,各种语法糖让我们更加方便地使用,但是也增加了复杂度(都是一些什么玩意每天升级让人一直得去学习)。但是我想告诉大家的是,IL语言改变不大,IL归根到底还是高级语言,有类,对象,方法,字段,属性,事件,委托等,也就是说不管C#再怎么升级,都是编译器变的魔术,最终生成的IL语言和前面版本的C#是差不多的。当然异步函数也是编译器变的一种魔术。

IL语言我们可以通过工具生成,这里我用的是ilspy,也就是dll生成IL,然后也有工具可以从IL生成C#代码,可以使用IL dasm。我们接下来把这个代码,生成IL。


这里我不解释IL语言了,这是另外一个章节需要讲的事情了。

今日头条文章有字数限制,接下来请看

C#核心-迭代器揭秘2

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表