【转】先说IEnumerable,我们每天用的foreach你真的懂它吗?
【转】先说IEnumerable,我们每天用的foreach你真的懂它吗?
我们先思考几个问题:
- 为什么在foreach中不能修改item的值?
- 要实现foreach需要满足什么条件?
- 为什么Linq to Object中要返回IEnumerable?
接下来,先开始我们的正文。
自己实现迭代器
.net中迭代器是通过IEnumerable和IEnumerator接口来实现的,今天我们也来依葫芦画瓢。
首先来看看这两个接口的定义:


并没有想象的那么复杂。其中IEnumerable只有一个返回IEnumerator的GetEnumerator方法。而IEnumerator中有两个方法加一个属性。
接下来开发画瓢,我们继承IEnumerable接口并实现:

下面使用原始的方式调用:

有朋友开始说了,我们平时都是通过foreache来取值的,没有这样使用过啊。好吧,我们来使用foreach循环:

为什么说基本上是等效的呢?我们先看打印结果,在看反编译代码。


由此可见,两者有这么个关系:

我们可以回答第一个问题了“为什么在foreach中不能修改item的值?”:

我们还记得IEnumerator的定义吗

接口的定义就只有get没有set。所以我们在foreach中不能修改item的值。
我们再来回答第二个问题:“要实现foreach需要满足什么条件?”:
必须实现IEnumerable接口?NO

我们自己写的MyIEnumerable删掉后面的IEnumerable接口一样可以foreach(不信?自己去测试)。
所以要可以foreach只需要对象定义了GetEnumerator无参方法,并且返回值是IEnumerator或其对应的泛型。细看下图:

也就是说,只要可以满足这三步调用即可。不一定要继承于IEnumerable。有意思吧!下次面试官问你的时候一定要争个死去活来啊,哈哈!
yield的使用
你肯定发现了我们自己去实现IEnumerator接口还是有些许麻烦,并且上面的代码肯定是不够健壮。对的,.net给我们提供了更好的方式。

你会发现我们连MyIEnumerator都没要了,也可以正常运行。太神奇了。yield到底为我们做了什么呢?

好家伙,我们之前写的那一大坨。你一个yield关键字就搞定了。最妙的是这块代码:

这就是所谓的状态机吧!
我们继续来看GetEnumerator的定义和调用:

我们调用GetEnumerator的时候,看似里面for循环了一次,其实这个时候没有做任何操作。只有调用MoveNext的时候才会对应调用for循环:

现在我想可以回答你“为什么Linq to Object中要返回IEnumerable?”:
因为IEnumerable是延迟加载的,每次访问的时候才取值。也就是我们在Lambda里面写的where、select并没有循环遍历(只是在组装条件),只有在ToList或foreache的时候才真正去集合取值了。这样大大提高了性能。
如:

这个时候得到了就是IEnumerable对象,但是没有去任何遍历的操作。(对照上面的gif动图看)
什么,你还是不信?那我们再来做个实验,自己实现MyWhere:

现在看到了吧。执行到MyWhere的时候什么动作都没有(返回的就是IEnumerable),只有执行到ToList的时候才代码才真正的去遍历筛选。
这里的MyWhere其实可以用扩展方法来实现,提升逼格。(Linq的那些查询操作符就是以扩展的形式实现的)[了解扩展方法]。
怎样高性能的随机取IEnumerable中的值

这段代码来源《深入理解C#》,个人觉得非常妙。贴出来给大家欣赏哈。
结束:
demo下载:http://pan.baidu.com/s/1dE94c1b
接下篇:《再讲IQueryable<T>,揭开表达式树的神秘面纱》
本文以同步至《C#基础知识巩固系列》
【转】先说IEnumerable,我们每天用的foreach你真的懂它吗?的更多相关文章
- 先说IEnumerable,我们每天用的foreach你真的懂它吗?
我们先思考几个问题: 为什么在foreach中不能修改item的值? 要实现foreach需要满足什么条件? 为什么Linq to Object中要返回IEnumerable? 接下来,先开始我们的正 ...
- C# 先说IEnumerable,我们每天用的foreach你真的懂它吗?
原文: http://www.cnblogs.com/zhaopei/p/5769782.html
- 可枚举接口的知识点(IEnumerable 接口)要使用foreach,就必须实现可枚举接口
- 15.IEnumerable和IEnumerator
先说IEnumerable,我们每天用的foreach你真的懂它吗? 阅读目录 自己实现迭代器 yield的使用 怎样高性能的随机取IEnumerable中的值 我们先思考几个问题: 为什么在fore ...
- 再讲IQueryable<T>,揭开表达式树的神秘面纱
接上篇<先说IEnumerable,我们每天用的foreach你真的懂它吗?> 最近园子里定制自己的orm那是一个风生水起,感觉不整个自己的orm都不好意思继续混博客园了(开个玩笑).那么 ...
- Foreach遍历
前天在项目中遇到一个问题,foreach遍历过程中修改responses中的对象,其中responses的类型:IEnumerable<Order>,代码如下: foreach (Orde ...
- LINQ学习系列-----2.2 迭代器
在学习本篇迭代器之前,强烈建议可以先学习一位具有工匠精神博主的文章,链接如下: 农码一生---先说IEnumerable,我们每天用的foreach你真的懂它吗? 本篇文章,是在该博主博文的基础上再次 ...
- 【转】再讲IQueryable<T>,揭开表达式树的神秘面纱
[转]再讲IQueryable<T>,揭开表达式树的神秘面纱 接上篇<先说IEnumerable,我们每天用的foreach你真的懂它吗?> 最近园子里定制自己的orm那是一个 ...
- 你可能不知道的陷阱, IEnumerable接口
1. IEnumerable 与 IEnumerator IEnumerable枚举器接口的重要性,说一万句话都不过分.几乎所有集合都实现了这个接口,Linq的核心也依赖于这个万能的接口.C语言的 ...
随机推荐
- spark快速开发之scala基础之1 数据类型与容器
写在前面 面向java开发者.实际上,具有java基础学习scala是很容易.java也可以开发spark,并不比scala开发的spark程序慢.但学习scala可有助于更快更好的理解spark.比 ...
- Householder矩阵,Givens矩阵
householder 矩阵相当于对某一空间中的元素(向量.矩阵)进行镜像变换,但是模值并不发生变化. H=I-2uuT householder矩阵有几个重要的性质: 1 : H-1 = H 2: ...
- linux命令学习之:read
read命令从键盘读取变量的值,通常用在shell脚本中与用户进行交互的场合.该命令可以一次读取多个变量的值,变量和输入的值都需要使用空格隔开.在read命令后面,如果没有指定变量名,读取的数据将被自 ...
- 线特征---LSD and LBD程序运行(一)
最近在看有关特征提取的线特征,暑期就看了相关的论文:<基于点线综合特征的双目视觉SLAM方法_谢晓佳>,最近呢,把里面有关线特征提取LSD和描述子LBD的代码跑了一遍,记录如下: [1]L ...
- bean生命周期_Junit测试使用factory模式
在面试某互联网_保险 公司, 被问到了spring中bean的生命周期,不禁联想到我们之前作 junit测试时,使用了Factory模式,而没有用Mock.
- 【APT】SqlServer游标使用
use [ElephantCredit] go begin transaction tran_bank; print '**脚本开始执行!'; declare @tran_error int , @n ...
- 解决video标签在微信中强制全屏、微信全屏播放(Android和IOS)
在video标签中加上代码: x5-playsinline="true" webkit-playsinline="true" playsinline=" ...
- TortoiseSVN设置Beyond Compare为版本比较、差异合并工具
打开乌龟的setting==>Diff Viewer 比较"D:\develop\Beyond Compare 4\BCompare.exe" %base %mine /ti ...
- VS2013中Nuget程序包管理器控制台使用入门(三)-项目实战(原创)
VS2013中Nuget程序包管理器控制台使用入门(三)-项目实战 1.给指定项目安装Newtonsoft.Json ,Version 4.5.11 PM> Install-Package Ne ...
- Linux移植之配置过程分析
在Linux移植之移植步骤中已经将Linux移植的过程罗列出来了,现在分析一下Linux的配置过程,将分析以下两个配置过程: 1.make s3c2410_defconfig分析 2.make men ...