IEnumerable 使用foreach 详解

  • 自己实现迭代器
  • yield的使用
  • 怎样高性能的随机取IEnumerable中的值

我们先思考几个问题:

  1. 为什么在foreach中不能修改item的值?
  2. 要实现foreach需要满足什么条件?
  3. 为什么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>,揭开表达式树的神秘面纱》

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏吴裕超

es6 Object的几个新方法

ES5 的 Object.preventExtensions 则可以阻止给对象添加新属性

973
来自专栏大内老A

深入理解C# 3.x的新特性(2):Extension Method[上篇]

在C#3.0中,引入了一些列新的特性,比如: Implicitly typed local variable, Extension method,Lambda ...

1886
来自专栏Golang语言社区

go语言:函数参数传递详解

参数传递是指在程序的传递过程中,实际参数就会将参数值传递给相应的形式参数,然后在函数中实现对数据处理和返回的过程。比较常见的参数传递有:值传递,按地址传递参数或...

1301
来自专栏PHP实战技术

你真的懂let和const吗?

在ES6之前我们脑海里应该只存在全局作用域和函数级作用域,没有块级作用域。那么为什么要引入块级作用域呢?

41411
来自专栏个人随笔

深入类的方法

一.C#关键字扩充解释:   1. new :     1)开辟空间     2)调用构造     3)实例化对象   2. this:     当前类的实例,...

2777
来自专栏大内老A

通过内存分析工具来证明字符串驻留机制

在这之前我写过一些文章来介绍关于字符串内存分配和驻留的文章,涉及到的观点主要有:字符串的驻留机制避免了对具有相同字符序列的字符串对象的重复创建;被驻留的字符串是...

19410
来自专栏程序员互动联盟

【专业技术】C++里面重要的几个关键字的用法

编者按: 这几个关键字非常重要,程序中经常见到他们的身影,但是确切意思有时候还需要多搜索下才能知道。笔者这里把它搬出来,也是希望大家引起重视,努力掌握它。 C+...

3587
来自专栏逸鹏说道

Python3 与 C# 扩展之~基础衍生

在线编程: https://mybinder.org/v2/gh/lotapp/BaseCode/master

1043
来自专栏编程

程序员C语言C加加新手小白入门基础最容易犯的17种错误,你中了几个?

相信这么努力的你 已经置顶了我 C语言是面向过程的,而C++是面向对象的 C和C++的区别: C是一个结构化语言,它的重点在于算法和数据结构。C程序的设计首要考...

2065
来自专栏菩提树下的杨过

“default关键字”与“序列化传输”的注意事项

注:此乃“流水帐”式的水文,营养成分较低,高手请自动无视以下内容,否则引起消化不良等症状等,一概不管 ^_^ c#自从3.0开始,提供了很多便捷的语法特性(俗称...

1845

扫码关注云+社区