foreach原理分析

  我们知道通常foreach可以实现对类型的遍历,但是foreach并不是针对所有类型都可以实现遍历的功能,那么我们可以思考这样的一个问题:foreach对类型实施遍历的依据条件是什么?它是通过什么方式来实现遍历的?

  下面我们自定义一个类型来尝试使用foreach进行遍历,看会发生什么样的现象,并且以此作为出发点来一点点分析foreach的原理。

1.自定义类型并使用foreach遍历

运行VS后编译器提示了错误,根据错误描述可以推断出foreach需要调用Person类型中的”GetEnumerator”方法。

2.在自定义类型中添加”GetEnumerator”方法

下一步我们根据编译器的要求在Person类型中添加”GetEnumerator”方法,添加该方法后又出现如下的错误提示:

根据编译器的错误提示,可以推断出Person类的GetEnumerator方法的返回值类型必须要有MoveNext方法和Current属性,示例中的object类型并没有MoveNext方法和Current属性。

3.添加MoveNext方法和Current属性

  编译器的错误提示要有MoveNext方法和Current属性,但是我们无法搞清楚这两个东西的具体实现形式,比如说MoveNext方法的返回值是什么样的?Current属性是只读还是可读可写的?

  此时我们可以去已经支持foreach的类型中查找GetEnumerator方法返回值类型,并对该类型的实现进行仿写并用于我们自定义类型当中。string类型作为常用类型并支持foreach遍历,下面我们查看该string的GetEnumerator方法返回值类型进行仿写。

仿写后编译通过:


以“数数”的思想来真正实现foreach遍历的功能

  在生活中小朋友们往往通过数数的形式来完成对数学的启蒙,foreach遍历的思想就类似于“数数”。小朋友数数往往需要通过双手来对事物进行数数,这就相当于GetEnumerator方法:其作用实际上是需要foreach当前遍历的类型调用该方法提供一个用于遍历当前的类型的“迭代计数器对象”,GetEnumerator方法的返回值类型:该类型其实就是用于foreach遍历当前类型的“迭代计数器对象”,类似于计数的双手(工具);Current属性:就相当于当前数到的对象(foreach遍历到的当前元素);MoveNext方法:就相当于“数数”中对元素不断的移动的动作,促使让“迭代计数器对象”的计数移至到下一位。

  到目前为止已经提供了编译器错误提示的所有成员,但实际运行是毫无意义的,因为通常编程中实现遍历的对象都是一个序列,但是目前示例中Person类型并不是且成员未包含序列。我们将为示例中Person类型添加一个序列,作为foreach“遍历的对象”。


实现“数数的功能”

1.遍历的数据对象

  添加foreach“遍历的数据对象”后又有问题来了,foreach如何访问到这个数据呢。我们可以将数据作为“迭代计数器对象”构造函数的参数在GetEnumerator方法中传递过去,然后“迭代计数器对象”中提供一个字段进行存储。

2.设置foreach遍历到的当前元素(Current属性)

  目前我们遍历的“对象”是一个string数组,所以要读取这个数组里的元素,我们需要一个下标索引来读取遍历到的当前元素,并作为Current属性值。下面我们将设置一个int类型值为-1的字段作为“读取下标”,然后在Current属性的get方法中通过下标索引器读取“当前遍历到的数据对象”。

3.MoveNext方法

  因为我们是通过下标去访问元素的,所以需要对下标进行递增进行变化,从而不断指向下一个元素从而到达累计。MoveNext方法是一个布尔的返回值类型,其主要目的是告知foreach当前遍历的数据对象是否还存在没有遍历到的元素,如果存在元素则不断递增下标索引并反会true,反之返回false并结束遍历,MoveNext方法的返回值相当于foreach遍历的前提条件。

  下面我们通过代码来实现MoveNext方法:

到目前为止我们已经将一个不具备foreach条件的自定义类型,通过自编码实现了foreach的基本要求。下面我们通过代码运行看看执行结果:

结果运行成功,并打印输出遍历的数据对象(Person中的数组)。

注意:遍历该数组我们并没有通过foreach直接对其进行的遍历,而是结合foreach的流程本质和基本要求来实现的遍历功能。


通过调试来分析foreach的遍历的原理

通过调试结果我们可以总结下foreach遍历主要依靠三个流程:

  1. foreach调用当前遍历类型的GetEnumerator方法创建一个“迭代计数器对象”,并将遍历的数据作为参数传递到对象构造函数中。(获取迭代计数器对象)
  2. “迭代计数器对象”调用MoveNext方法将索引下标递增(第一次递增为0),如果递增下标大于数组长度则代表已经遍历完。(调用MoveNext方法)
  3. MoveNext方法返回true,代表还有元素需要遍历,使用当前下标在数据中获取元素并设置为Current属性值。(获取Current属性)

基于foreach的原理思想,我们还可以将遍历写成如下形式:

foreach的写法其实就是调用上面的代码片段,从而实现的一种“语法糖”。


C#中基于原理的实现方式

  在上文中我们借鉴string类型中的GetEnumerator方法来参照实现“迭代计数器对象”当中的MoveNext方法和Current属性。

  在.net中看某个类型是否支持使用foreach进行遍历,其实接可以看该类型和该类型的“迭代计数器”是否都实现了IEnumerable接口。IEnumerable接口中的成员就包含了foreach实现的原理和需要调用的成员。

图一:

图二:


示例源码

1   class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5
 6             Person person = new Person();
 7             foreach (var item in person)
 8             {
 9                 Console.WriteLine(item);
10             }
11
12             var Enumerator = person.GetEnumerator();
13             while (Enumerator.MoveNext())
14             {
15                 Console.WriteLine(Enumerator.Current);
16             }
17
18         }  // END Main()
19
20     }

Main

1    class Person
 2     {
 3         string[] Datas = new string[] { "张三","李四","王五"};
 4
 5         public PersonEnumerator GetEnumerator()
 6         {
 7             return new PersonEnumerator(Datas);
 8         }
 9     }
10
11     /// <summary>
12     /// 迭代计数器
13     /// </summary>
14     class PersonEnumerator
15     {
16         public PersonEnumerator(string[] datas) { this.Datas = datas; }
17
18         /// <summary>
19         /// 遍历的数据对象
20         /// </summary>
21         private string[] Datas;
22
23         private int index = -1;
24
25         /// <summary>
26         /// 当前遍历到的元素
27         /// </summary>
28         public string Current {
29             get { return Datas[index]; }
30         }
31
32         /// <summary>
33         /// 将记录指针移至下一条
34         /// </summary>
35         /// <returns>是否存在尚未遍历的元素</returns>
36         public bool MoveNext()
37         {
38             index++;
39             return index < Datas.Length;
40         }
41
42     }

Model

(0)

相关推荐

  • IEnumerator、IEnumerable傻傻分不清楚?

    IEnumerator.IEnumerable这两个接口单词相近.含义相关,傻傻分不清楚. 入行多年,一直没有系统性梳理这对李逵李鬼. 最近本人在怼着why神的<其实吧,LRU也就那么回事> ...

  • C# 便捷实现可迭代对象间的赋值

    问题起源于本人的一个练手的扑克牌程序:洗完牌之后要发给场上的三人. 只发给单个人的时候用 foreach 循环一下就好了,但三个人就有点麻烦了. 牌组用list保存你可能会想到这样写: for (in ...

  • 电水壶的电路原理分析与检测

    电水壶的基本功能是烧水.电水壶根据结构分为一体式和分体式两种,根据功能分为非保温型和保温型两种. 一.分体非保温式电水壶的检测 下面以格来德 WEF-115S 电水壶为例,介绍使用万用表检修分体非保温 ...

  • 豆浆机电路原理分析与故障检测

    下面以九阳 JYDZ-22 型豆浆机为例,介绍用万用表检修豆浆机故障的方法与技巧.该机电路由电源电路.微处理器电路.打浆电路.加热电路构成,如下图所示. 提示:改变图中 R19 的阻值,该电路板就可以 ...

  • 24V开关电源电路构成几工作原理分析

    电路以UC3842振荡芯片为,构成逆变.整流电路.UC3842一种高性能单端输出式电流控制型脉宽调制器芯片,相关引脚功能及内部电路原理已有介绍,此处从略.AC220V电源经共模滤波器L1引入,能较好抑 ...

  • EIS和OIS有啥差别?OIS光学防抖原理分析

    描述 翻阅智能手机的相册,我们总能看到一些拍糊的照片或视频.问题来了,如今哪怕是千元价位的手机都能用上4800万像素的索尼IMX586高端传感器,为何依旧无法保证每一张照片.每一段视频都是无比清晰的呢 ...

  • 开关电源工作原理分析及图解

    描述 开关电源就是用通过电路控制开关管进行高速的导通与截止. 将直流电转化为高频率的交流电提供给变压器进行变压,从而产生所需要的一组或多组电压!转为高频交流电的原因是高频交流在变压器变压电路中的效率要 ...

  • BUCK/BOOST电路原理分析

    描述 Buck变换器:也称降压式变换器,是一种输出电压小于输入电压的单管不隔离直流变换器. 图中,Q为开关管,其驱动电压一般为PWM(Pulse width modulation脉宽调制)信号,信号周 ...

  • BUCK电路工作原理分析详细阐述

    在电子电路中,电源一般分为两类,一类是线性电源,一类是开关电源.线性电源具有噪声小的优点.开关电源虽然噪大,但是具有效率高.热损小的优点. 开关电源还可以细分为降压型.升压型和升降压三类.也可按照隔离 ...

  • 8K视频指的什么?8K视频处理和工作原理分析

    随着视频技术的不断发展,分辨率从480P发展到1080P;当我们还没有完全意识到4K电视将一统天下的时候, 8K的直播就已开始.8K视频要求需要处理每帧约33M像素的数据量,海量的数据处理为目前的视频 ...

  • 竞价排量:集合竞价图红绿量柱的多空双方买卖意愿原理分析(图解)

    一.竞价排量的定义 竞价排量指的是:9:25分开盘前,集合竞价时撮合成交量的排列情况.这个量反应了持股股民(里面的股民)的卖出欲望,也反应了非持股股民(外面的股民)的买入欲望.卖出欲望强烈就会下跌,买 ...