C# 使用IENUMERABLE,YIELD

前言

上篇文章中我得出结论,遍历迭代器修改迭代器中项目的值未生效,是因为使用了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的更多相关文章

  1. yield return 和 yield break

    //yield return 返回类型必须为 IEnumerable.IEnumerable<T>.IEnumerator 或 IEnumerator<T>. static I ...

  2. 说下IEnumerable相关的

    IEnumerable 我们每天都在使用foreach进行遍历,今天讨论下面三个常见的问题: 为什么在foreach中不能修改item的值 要实现foreach需要满足什么条件 为什么Linq to ...

  3. Linq的使用场景简介和认识

    一:C#的一个分支Linq 二:学Linq需要有一些基础知识 1. var 隐式类型 2. 匿名类型/匿名方法 3. 自动属性 4. 委托/泛型的委托 5. lambda 6. 扩展方法 7. 对象初 ...

  4. 从yield关键字看IEnumerable和Collection的区别

    C#的yield关键字由来以久,如果我没有记错的话,应该是在C# 2.0中被引入的.相信大家此关键字的用法已经了然于胸,很多人也了解yield背后的“延迟赋值”机制.但是即使你知道这个机制,你也很容易 ...

  5. C# 使用IEnumerable,yield 返回结果,同时使用foreach时,在循环内修改变量的值无效(二)

    前言 在上篇文章中我得出结论,遍历迭代器修改迭代器中项目的值未生效,是因为使用了yield return,并且每次遍历迭代器都执行返回迭代器的方法.这篇文章是接着上篇文章,从代码实现的角度来验证出现这 ...

  6. C# ~ 从 IEnumerable / IEnumerator 到 IEnumerable<T> / IEnumerator<T> 到 yield

    IEnumerable / IEnumerator 首先,IEnumerable / IEnumerator 接口定义如下: public interface IEnumerable /// 可枚举接 ...

  7. IEnumerable、IEnumerator与yield的学习

    我们知道数组对象可以使用foreach迭代进行遍历,同时我们发现类ArrayList和List也可以使用foreach进行迭代.如果我们自己编写的类也需要使用foreach进行迭代时该怎么办呢? IE ...

  8. c#yield,IEnumerable,IEnumerator

    foreach 在编译成IL后,实际代码如下: 即:foreach实际上是先调用可枚举对象的GetEnumerator方法,得到一个Enumerator对象,然后对Enumerator进行while循 ...

  9. IEnumerable<T>作为方法返回值类型——建议通过yield return返回

    若IEnumerable<T>作为方法返回值的类型,则建议使用“迭代”模式(yield return) private IEnumerable<TwoLevelTreeNodeVie ...

随机推荐

  1. Android.mk的用法和基础

    一个Android.mk file用来向编译系统描述你的源代码.具体来说:该文件是GNU Makefile的一小部分,会被编译系统解析一次或多次.你可以在每一个Android.mk file中定义一个 ...

  2. 大一C语言结课设计之《简单计算器》

    /*===============================================*\ ** 设计目的:简单计算器,计算形如10*(20.2-30.6)+5.0/2的表达式值 ** 简 ...

  3. JavaScript 高级程序设计(第3版)笔记——chapter5:引用类型(基本包装类型部分)

    一.介绍 为了方便操作基本类型值,ECMAScript还提供了3个特殊的引用类型:Boolean, Number, String. 实际上,每当读取一个基本类型值得时候,后台就会创建一个对应的基本包装 ...

  4. Java thread中对异常的处理策略

    转载:http://shmilyaw-hotmail-com.iteye.com/blog/1881302 前言 想讨论这个话题有一段时间了.记得几年前的时候去面试,有人就问过我一个类似的问题.就是j ...

  5. Oracle数据库的安装详解

    1.写在安装前的话 可能有很多的菜鸟十分害怕大型软件的安装,因为安装过程中的一些错误很让他们头疼.下面我就写一个教程,希望能对大家有帮助,在安装ORACLE之前给大家一点点的意见: (1)尽量要安装L ...

  6. [转载]CSS 创作指南(Beta)(css规范)

    当年还在纠结各种规范的时候,不知道从哪里翻到这个,就让我脱离了css这个规范的苦海了... 反正就是团队和项目合作说的算,选择合适的进行使用就可以了,见到合适的文章,我也会转载过来的 来源 https ...

  7. hdu 3641 Treasure Hunting 强大的二分

    /** 大意:给定一组ai,bi . m = a1^b1 *a2^b2 * a3^ b3 * a4^b4*...*ai^bi 求最小的x!%m =0 思路: 将ai 质因子分解,若是x!%m=0 那么 ...

  8. (IOS)签名Demo

    思路是将每一次按下屏幕的touch move时的点存到一个数组里,即一个数组相当于一个笔画:再将该代表笔画的数组保存到一个大数组中,每组每次touch的移动都历遍大数组和笔画数组,将点于点之间连接起来 ...

  9. BZOJ 1644: [Usaco2007 Oct]Obstacle Course 障碍训练课

    题目 1644: [Usaco2007 Oct]Obstacle Course 障碍训练课 Time Limit: 5 Sec  Memory Limit: 64 MB Description 考虑一 ...

  10. 一组开源 HTML5 Apps

    一组用"画app吧"开发的 HTML5 Apps,默认使用FirefoxOS设备,其实它们都可以在像Android/IPhone/WindowsPhone8/BlackBerry/ ...