C# 使用IENUMERABLE,YIELD

前言

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

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

正文

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

  1. public void YieldTest()
  2. {
  3. var list = GetEnumerable();
  4. for (int i = 0; i < 100; i++)
  5. {
  6. foreach (var test in list)
  7. {
  8. test.atr1 = 0;
  9. test.atr2 = "11";
  10. }
  11. }
  12. }
  13. public IEnumerable<ListTest> GetEnumerable()
  14. {
  15. for (int i = 0; i < 2; i++)
  16. {
  17. yield return new ListTest()
  18. {
  19. atr1 = i + 1,
  20. atr2 = string.Format("test{0}", i + 1)
  21. };
  22. }
  23. }

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

  1. public void YieldTest()
  2. {
  3. IEnumerable<ListTest> enumerable;
  4. int num;
  5. ListTest test;
  6. IEnumerator<ListTest> enumerator;
  7. bool flag;
  8. enumerable = this.GetEnumerable();
  9. num = 0;
  10. goto Label_005A;
  11. Label_000C:
  12. enumerator = enumerable.GetEnumerator();
  13. Label_0015:
  14. try
  15. {
  16. goto Label_0034;
  17. Label_0017:
  18. test = enumerator.Current;
  19. test.atr1 = 0;
  20. test.atr2 = "11";
  21. Label_0034:
  22. if (enumerator.MoveNext() != null)
  23. {
  24. goto Label_0017;
  25. }
  26. goto Label_0054;
  27. }
  28. finally
  29. {
  30. Label_0042:
  31. if ((enumerator == null) != null)
  32. {
  33. goto Label_0053;
  34. }
  35. enumerator.Dispose();
  36. Label_0053:;
  37. }
  38. Label_0054:
  39. num += 1;
  40. Label_005A:
  41. if ((num < 100) != null)
  42. {
  43. goto Label_000C;
  44. }
  45. return;
  46. }
  47. public void YieldTest()
  48. {
  49. IEnumerable<ListTest> enumerable;
  50. int num;
  51. ListTest test;
  52. IEnumerator<ListTest> enumerator;
  53. bool flag;
  54. enumerable = this.GetEnumerable();
  55. num = 0;
  56. goto Label_005A;
  57. Label_000C:
  58. enumerator = enumerable.GetEnumerator();
  59. Label_0015:
  60. try
  61. {
  62. goto Label_0034;
  63. Label_0017:
  64. test = enumerator.Current;
  65. test.atr1 = 0;
  66. test.atr2 = "11";
  67. Label_0034:
  68. if (enumerator.MoveNext() != null)
  69. {
  70. goto Label_0017;
  71. }
  72. goto Label_0054;
  73. }
  74. finally
  75. {
  76. Label_0042:
  77. if ((enumerator == null) != null)
  78. {
  79. goto Label_0053;
  80. }
  81. enumerator.Dispose();
  82. Label_0053:;
  83. }
  84. Label_0054:
  85. num += 1;
  86. Label_005A:
  87. if ((num < 100) != null)
  88. {
  89. goto Label_000C;
  90. }
  91. return;
  92. }
  93. public IEnumerable<ListTest> GetEnumerable()
  94. {
  95. <GetEnumerable>d__12 d__;
  96. IEnumerable<ListTest> enumerable;
  97. d__ = new <GetEnumerable>d__10(-2);
  98. d__.<>4__this = this;
  99. enumerable = d__;
  100. Label_0013:
  101. return enumerable;
  102. }

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

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

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

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

  1. private bool MoveNext()
  2. {
  3. bool flag;
  4. int num;
  5. bool flag2;
  6. num = this.<>1__state;
  7. switch (num)
  8. {
  9. case 0:
  10. goto Label_0019;
  11. case 1:
  12. goto Label_0017;
  13. }
  14. goto Label_001B;
  15. Label_0017:
  16. goto Label_008B;
  17. Label_0019:
  18. goto Label_0020;
  19. Label_001B:
  20. goto Label_00AF;
  21. Label_0020:
  22. this.<>1__state = -1;
  23. this.<i>5__11 = 0;
  24. goto Label_00A1;
  25. Label_0031:
  26. this.<>g__initLocalf = new ListTest();
  27. this.<>g__initLocalf.atr1 = this.<i>5__11 + 1;
  28. this.<>g__initLocalf.atr2 = string.Format("test{0}", (int) (this.<i>5__11 + 1));
  29. this.<>2__current = this.<>g__initLocalf;
  30. this.<>1__state = 1;
  31. flag = 1;
  32. goto Label_00B3;
  33. Label_008B:
  34. this.<>1__state = -1;
  35. this.<i>5__11 += 1;
  36. Label_00A1:
  37. if ((this.<i>5__11 < 2) != null)
  38. {
  39. goto Label_0031;
  40. }
  41. Label_00AF:
  42. flag = 0;
  43. Label_00B3:
  44. return flag;
  45. }

到这里我们便可以理解本文开头的两个问题了:
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. BZOJ3231(矩阵连乘,稍有点复杂)

    题目:3231: [Sdoi2008]递归数列 题意: 一个由自然数组成的数列按下式定义:   对于i <= k:ai = bi 对于i > k: ai = c1ai-1 + c2ai-2 ...

  2. cocos2d基础入门

    HelloCpp中Classes目录下放开发者自己的类: win32:平台相关,coco2d已默认创建:coco2d-x目录下,samples/cpp/HelloCpp/(工程根目录)图片放置位置:根 ...

  3. Enze fifth day(循环语句2)

    又是新的一周开始了,我还在云和学院继续学习.因为想要急切的想学会更多的知识,所以我有些急.可是我越急就越容易出错,这应该就是所谓的欲速则不达吧.这一周,我要重新把控好自己的一切,尽我最大的努力来学习! ...

  4. springdata+redis配置详解

    springdata设计初衷是位简化数据类型和数据的持久化存储,它并不局限是关系型数据库还是nosql数据库,都提供了简化的数据库连接,让数据获取变得更加的简单.所有这些的实现有统一的api提供. 本 ...

  5. sql差异

    类别 MS SQL Server My SQL PG SQL Oracle Access  自增  identity(1,1) auto_increment ALTER TABLE 'tableNam ...

  6. ASP.NET jQuery 随笔 在TextBox里面阻止复制、剪切和粘贴事件

    当用户要输入一些密码.信用卡信息和银行账号等敏感信息,用户更希望手工通过键盘敲入数据,而好过通过剪贴板复制粘贴. 我们先来看下实现后的效果: <%@ Page Language="C# ...

  7. QT学习 之 QwtPlot(数学绘图)

    QT对于统计图像.函数图像等的绘制是没有相关组件的帮助的,只有利用手工绘制图片. QwtPlot是用来绘制二维图像的widget,继承自QFrame 和 QwtPlotDict.不过严格的说来,它只是 ...

  8. [每日一题] 11gOCP 1z0-052 :2013-09-14 repeated parsing activity.................................A70

    转载请注明出处:http://blog.csdn.net/guoyjoe/article/details/11699299 正确答案:D SQL语句的执行过程: 1.客户端输入sql语句update ...

  9. Service初步了解

    1.Service什么 Service它是一个应用程序组件,Android其中的四个核心组件之间 Service没有图形界面 通过经常使用来处理一些比较长耗时的操作 可以使用Service更新Cont ...

  10. Swift 自定义炫酷下拉刷新效果

    先来看下效果 下拉刷新 其实下拉刷新没大家想得那么难.本文已第二个为例子.给大家讲解下下拉刷新的做法(完整代码后面会放上) 首先,先搞一个single View Application .然后进Mai ...