前言

上篇文章中我得出结论,遍历迭代器修改迭代器中项目的值未生效,是因为使用了yield return,并且每次遍历迭代器都执行返回迭代器的方法。这篇文章是接着上篇文章,从代码实现的角度来验证出现这种情况的原因。
首先介绍下一种查看代码实现的一种方法:使用Reflector反编译dll或者exe文件我们可以看到里面的代码,在下面的配置中可以选择代码实现的C#版本:
Tools->Options

这里我们选择为None,这时Reflector将不会对反编译的代码进行优化,将最原始的实现方法展现给我们,我们在这种代码中可以看到很多C#相对底层的实现。

正文

下面是我们要查看的代码,一个是获取迭代器返回多个ListTest类的示例,另一个是对迭代器进行多次循环并修改代器内容:

public void YieldTest()
{
var list = GetEnumerable();
for (int i = 0; i < 100; i++)
{
foreach (var test in list)
{
test.atr1 = 0;
test.atr2 = "11";
}
}
} public IEnumerable<ListTest> GetEnumerable()
{
for (int i = 0; i < 2; i++)
{
yield return new ListTest()
{
atr1 = i + 1,
atr2 = string.Format("test{0}", i + 1)
};
}
}

下面是这两段代码反编译的结果:

public void YieldTest()
{
IEnumerable<ListTest> enumerable;
int num;
ListTest test;
IEnumerator<ListTest> enumerator;
bool flag;
enumerable = this.GetEnumerable();
num = 0;
goto Label_005A;
Label_000C:
enumerator = enumerable.GetEnumerator();
Label_0015:
try
{
goto Label_0034;
Label_0017:
test = enumerator.Current;
test.atr1 = 0;
test.atr2 = "11";
Label_0034:
if (enumerator.MoveNext() != null)
{
goto Label_0017;
}
goto Label_0054;
}
finally
{
Label_0042:
if ((enumerator == null) != null)
{
goto Label_0053;
}
enumerator.Dispose();
Label_0053:;
}
Label_0054:
num += 1;
Label_005A:
if ((num < 100) != null)
{
goto Label_000C;
}
return;
} public void YieldTest()
{
IEnumerable<ListTest> enumerable;
int num;
ListTest test;
IEnumerator<ListTest> enumerator;
bool flag;
enumerable = this.GetEnumerable();
num = 0;
goto Label_005A;
Label_000C:
enumerator = enumerable.GetEnumerator();
Label_0015:
try
{
goto Label_0034;
Label_0017:
test = enumerator.Current;
test.atr1 = 0;
test.atr2 = "11";
Label_0034:
if (enumerator.MoveNext() != null)
{
goto Label_0017;
}
goto Label_0054;
}
finally
{
Label_0042:
if ((enumerator == null) != null)
{
goto Label_0053;
}
enumerator.Dispose();
Label_0053:;
}
Label_0054:
num += 1;
Label_005A:
if ((num < 100) != null)
{
goto Label_000C;
}
return;
} public IEnumerable<ListTest> GetEnumerable()
{
<GetEnumerable>d__12 d__;
IEnumerable<ListTest> enumerable;
d__ = new <GetEnumerable>d__10(-2);
d__.<>4__this = this;
enumerable = d__;
Label_0013:
return enumerable;
}

首先我们看下YieldTest函数的代码,变长了很多,其实理清楚里面goto语句的话,逻辑还是很清晰的,这里我们看出下面几个点:

  • for循环是通过判断步进值num和使用goto语句来实现的。
  • foreach关键字的实现逻辑是:使用迭代器的Current属性获取当前项执行操作,然后调用MoveNext()方法使Current属性指向下一项,然后goto语句循环处理。

再来看GetEnumerable()方法,这里就比较奇怪了,代码返回了一个<GetEnumerable>d__10类的实例,并没有我函数中的代码逻辑,而且我代码中也没有这个类,这个类是.net为我们自动生成的,并且实现了迭代器接口:

YieldTest函数中便使用了这个迭代器,迭代器的Current属性便是我们代码中返回的ListTest类,而我代码的逻辑其实在MoveNext()方法中:

private bool MoveNext()
{
bool flag;
int num;
bool flag2;
num = this.<>1__state;
switch (num)
{
case 0:
goto Label_0019; case 1:
goto Label_0017;
}
goto Label_001B;
Label_0017:
goto Label_008B;
Label_0019:
goto Label_0020;
Label_001B:
goto Label_00AF;
Label_0020:
this.<>1__state = -1;
this.<i>5__11 = 0;
goto Label_00A1;
Label_0031:
this.<>g__initLocalf = new ListTest();
this.<>g__initLocalf.atr1 = this.<i>5__11 + 1;
this.<>g__initLocalf.atr2 = string.Format("test{0}", (int) (this.<i>5__11 + 1));
this.<>2__current = this.<>g__initLocalf;
this.<>1__state = 1;
flag = 1;
goto Label_00B3;
Label_008B:
this.<>1__state = -1;
this.<i>5__11 += 1;
Label_00A1:
if ((this.<i>5__11 < 2) != null)
{
goto Label_0031;
}
Label_00AF:
flag = 0;
Label_00B3:
return flag;
}

到这里我们便可以理解本文开头的两个问题了:
1、使用yield return时,在foreach中修改迭代器的内容不生效:

调用yield return的方法时只是返回了一个迭代器的实例,并没有真正执行方法里的逻辑,当我们循环迭代器调用MoveNext()方法时,才会真正执行我们写代码逻辑,而且每次循环迭代器都会执行MoveNext()方法获取新的实例,所以每次操作都不会影响到下一次的循环。

2、每次循环迭代器都会执行GetEnumerable()函数:

因为每次执行的是MoveNext()方法,而原本GetEnumerable()中的代码已经在MoveNext()方法中了。

下面是我对yield的一些思考:

就正常需求来说是没有必要使用yield的,多出的一些预料之外的影响也会把我们带到坑里;我觉得比较有用的使用情况是:多线程批量处理的时候,获取到一个数据便调用线程处理,一边处理一边获取新数据,相对于获取到所有数据在分配给线程处理是可以提高性能,特别是获取数据需要耗时的情况。

本人水平有限,能力一般,希望可以学到更多的知识,有什么错误的地方欢迎指出!

C# 使用IEnumerable,yield 返回结果,同时使用foreach时,在循环内修改变量的值无效(二)的更多相关文章

  1. python yield返回多个值

    yield可以返回多个值到setup函数中去,但是需要用括号括起来,然后下面具体的函数接受到传值就不需要每次都实例化了. 举例如下: @pytest.fixture()def setup(driver ...

  2. Python连载39-生成器、next函数、yield返回值

    ​一.生成器 1.定义(generator):一边循环一边计算下一个元素的机制/算法 2.满三个条件 (1)每次调用都能产生出for循环需要的下一个元素 (2)如果达到最后一个后,能够爆出StopIt ...

  3. foreach 和 for 循环的区别

    foreach 依赖 IEnumerable. 第一次 var a in GetList() 时 调用 GetEnumerator 返回第一个对象 并 赋给a, 以后每次再执行 var a in Ge ...

  4. for ,foreach ,map 循环的区别

    一.for循环 1.for - 循环代码块一定的次数 遍历数组最常用到的for循环,是最为熟知的一种方法 for (var i=0; i<5; i++) { x=x + "The nu ...

  5. 第三百四十一节,Python分布式爬虫打造搜索引擎Scrapy精讲—编写spiders爬虫文件循环抓取内容—meta属性返回指定值给回调函数—Scrapy内置图片下载器

    第三百四十一节,Python分布式爬虫打造搜索引擎Scrapy精讲—编写spiders爬虫文件循环抓取内容—meta属性返回指定值给回调函数—Scrapy内置图片下载器 编写spiders爬虫文件循环 ...

  6. 二十 Python分布式爬虫打造搜索引擎Scrapy精讲—编写spiders爬虫文件循环抓取内容—meta属性返回指定值给回调函数—Scrapy内置图片下载器

    编写spiders爬虫文件循环抓取内容 Request()方法,将指定的url地址添加到下载器下载页面,两个必须参数, 参数: url='url' callback=页面处理函数 使用时需要yield ...

  7. 调试台自动多出现一个'&#65279;' ,我 用uploadify上传图片时,在给页面写入一个返回值为图片名称的变量的值的时候值的前面始终多出现一个'&#65279;'

    对你有助请点赞,请顶,不好请踩------送人玫瑰,手留余香! 15:54 2016/3/12用uploadify上传图片时,在给页面写入一个返回值为图片名称的变量的值的时候值的前面始终多出现一个' ...

  8. 浅析PHP中for与foreach两个循环结构遍历数组的区别

    遍历一个数组是编程中最常见不过的了,这里跟大家讨论下for和foreach两种方法.用这两种方法执行遍历的场景太多太多了,这里我们只针对以下两个数组作为例子来讨论.所谓管中窥豹,多少能理清一点两者的区 ...

  9. springMVC 返回类型选择 以及 SpringMVC中model,modelMap.request,session取值顺序

    springMVC 返回类型选择 以及 SpringMVC中model,modelMap.request,session取值顺序 http://www.360doc.com/content/14/03 ...

随机推荐

  1. [转]JavaScript快速检测浏览器对CSS3特性的支持

    转自:https://yuguo.us/weblog/detect-css-support-in-browsers-with-javascript/ ------------------------- ...

  2. 爹地,我找到了!15个极好的Linux find命令示例

    爹地,我找到了!15个极好的Linux find命令示例 http://blog.jobbole.com/48931/ 妈咪,我找到了!15个实用的Linux find命令示例 http://blog ...

  3. org.apache.catalina.webresources.Cache.getResource Unable to add the resource

    org.apache.catalina.webresources.Cache.getResource Unable to add the resource at xxx to the cache be ...

  4. 编译MVC解决方案老出现这个问题的原因

    Server Error in '/' Application. The view at '~/Views/Home/Index.cshtml' must derive from WebViewPag ...

  5. [转载] 百度上传&下载脚本

    下载百度网盘中的文件,最简单的办法是使用wget命令.找到百度网盘文件,点击下载,然后找到相应的下载地址,复制链接地址即可.但这种方法有一个bug,因为百度云网盘访问文件的链接地址是动态的,当文件比较 ...

  6. https://developers.google.com/maps/documentation/javascript/examples/places-autocomplete-addressform

    https://developers.google.com/maps/documentation/javascript/examples/places-autocomplete-addressform

  7. 转-httpd 2.4.4 + mysql-5.5.28 + php-5.4.13编译安装过程

    一.编译安装apache 1.解决依赖关系 httpd-2.4.4需要较新版本的apr和apr-util,因此需要事先对其进行升级.升级方式有两种,一种是通过源代码编译安装,一种是直接升级rpm包.这 ...

  8. [NOI 2006] 最大获利 80分

    最后两点怎么搞都要30s+,但是我不会什么优化啊…暂时就这样吧.Dinic的时间复杂度是O(N^2*M) 这题和TDL的幼儿园模板是一样的. 这次写网络流给自己计时了,大约是40min左右,后来都跑去 ...

  9. 第九篇.bootstrap导航

    创建一个标签式的导航菜单的步骤是: 在ul标签上加上class  nav 再ul标签上加上 class .nav-tabs. 在li标签上加上 active表示激活该项 <ul class=&q ...

  10. c语言实现牛顿迭代法

    #include<stdio.h> #include<stdlib.h> #include<math.h> #include<float.h> #inc ...