转自:http://www.cnblogs.com/artech/archive/2010/10/28/yield.html

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

目录
一、一个很简单的例子
二、简单谈谈“延迟赋值”
三、从反射的代码帮助我们更加直接的了解yield导致的延迟赋值
四、如果需要“立即赋值”怎么办?
后记

一、一个很简单的例子

下面是一个很简单的例子:Vector为自定义表示二维向量的类型,Program的静态方法GetVetors方法获取以类型为IEnumerable<Vector> 表示的Vector列表,而方法通过yield关键字返回三个Vectior对象。在Main方法中,将GetVetors方法的返回值赋值给一个变量,然后对每一个Vector对象的X和Y进行重新赋值,最后将每一个Vector的信息输出来。从最后的输出我们不难看出,我们对Vector的重新赋值无效,最终的每一个Vector元素依旧“保持”着初始值。

namespace ConsoleApplication1
{class Program { static void Main(string[] args) { IEnumerable<Vector> vectors = GetVectors(); foreach (var vector in vectors) { vector.X = 4; vector.Y = 4; } foreach (var vector in vectors) { Console.WriteLine(vector); } }static IEnumerable<Vector> GetVectors() { yield return new Vector(1, 1); yield return new Vector(2, 3); yield return new Vector(3, 3); } }public class Vector { public double X { get; set; } public double Y { get; set; } public Vector(double x, double y) { this.X = x; this.Y = y; } public override string ToString() { return string.Format("X = {0}, Y = {1}", this.X, this.Y); }}
}

输出结果:

二、简单谈谈“延迟赋值”

对于上面的现象,很多人一眼就可以看出这是由于yield背后的“延迟赋值”机制导致,但是不可否认我们会不经意间犯这种错误。为了让大家对这个问题有稍微深刻的认识,我们还是简单来谈谈“延迟赋值”。延迟赋值(Delay|Lazy Evaluation)又被称为延迟计算。为了避免不必要的计算导致的性能损失,和LINQ查询一样,yield关键字并不会导致后值语句的立即执行,而是转换成一个“表达式”。只有等到需要的那一刻(进行迭代)的时候,表达式被才被执行。

针对上面这个例子,我们对其进行简单的修改来验证“延迟赋值”的存在。我我们只需要在Vector的构造函数中添加一行语句:Console.WriteLine("Vector object is instantiated.");。从运行后的结过我们可以看出,Vector对象被创建了6次,来自于两次迭代。一次是对Vector元素的重新赋值,另一次源自对Vector元素的输出。由于两次迭代造作的并不是同一批对象,才会导致X和Y属性依然“保持”着原始的值。

    public class Vector { public double X { get; set; } public double Y { get; set; } public Vector(double x, double y) {Console.WriteLine("Vector object is instantiated.");this.X = x; this.Y = y; } public override string ToString() { return string.Format("X = {0}, Y = {1}", this.X, this.Y); }}

输出结果:

三、从反射的代码帮助我们更加直接的了解yield导致的延迟赋值

通过Reflector对编译后的代码进行发射,可以为我们更加“赤裸”地揭示yield导致的延迟赋值,下面的代码片断是对Program类型的“本质”反映。

internal class Program   {   private static IEnumerable<Vector> GetVectors()   {   return new <GetVectors>d__0(-2);   }   private static void Main(string[] args)   {IEnumerable<Vector> vectors = GetVectors();  foreach (Vector vector in vectors)  { vector.X = 4.0; vector.Y = 4.0;  }  foreach (Vector vector in vectors)  {  Console.WriteLine(vector);  }  }      }

从上面的代码我们可以看到,通过yield关键字实现的GetVectors方法最终返回值是一个<GetVectors>d__0类型的对象,该对象定义如下:

  [CompilerGenerated]   private sealed class <GetVectors>d__0 : IEnumerable<Vector>, IEnumerable, IEnumerator<Vector>, IEnumerator, IDisposable  {  private int <>1__state;  private Vector <>2__current;   private int <>l__initialThreadId;   [DebuggerHidden]   public <GetVectors>d__0(int <>1__state);  private bool MoveNext();  [DebuggerHidden]  IEnumerator<Vector> IEnumerable<Vector>.GetEnumerator();  [DebuggerHidden]  IEnumerator IEnumerable.GetEnumerator();  [DebuggerHidden]  void IEnumerator.Reset();  void IDisposable.Dispose();  Vector IEnumerator<Vector>.Current { [DebuggerHidden] get; } object IEnumerator.Current { [DebuggerHidden] get; }   }

这是一个实现了众多接口的类型,实现的接口包括:IEnumerable<Vector>, IEnumerable, IEnumerator<Vector>, IEnumerator, IDisposable。<GetVectors>d__0 类大部分成员都没有复杂的逻辑,唯一值得一提的就是MoveNext方法。从中我们清楚地但到,对Vector对象的创建发生在每一个迭代中。

private bool MoveNext()   { switch (this.<>1__state)  {   case 0:  this.<>1__state = -1;  this.<>2__current = new Vector(1.0, 1.0);  this.<>1__state = 1;  return true; case 1: this.<>1__state = -1;  this.<>2__current = new Vector(2.0, 3.0); this.<>1__state = 2; return true; case 2: this.<>1__state = -1; this.<>2__current = new Vector(3.0, 3.0); this.<>1__state = 3;return true; case 3: this.<>1__state = -1; break; }  return false; }

四、如果需要“立即赋值”怎么办?

有时候我们不需要“延迟赋值”,而需要“立即赋值”,因为调用着需要维护它们的状态,那该怎么办呢?有人说,不用yield不久得到吗?但是有的情况下,我们需要调用别人提供的API来获取IEnumerable<T>对象,我们不清楚对方有没有使用yield关键字。在这种情况我个人常用的做法就是调用ToArray或者ToList将其转换成T[]或者List<T>,进而进行强制赋值。由于它们也实现了接口IEnumerable<T>,所以不会存在什么问题。同样是对于我们的例子,我们在对GetVectors方法的返回值进行变量赋值的时候的调用ToArray或者ToList方法,我们就能对元素进行有效赋值。

class Program  {  //......  static void Main(string[] args) {  IEnumerable<Vector> vectors = GetVectors().ToList(); foreach (var vector in vectors)  {  vector.X = 4;  vector.Y = 4;  } foreach (var vector in vectors)  {  Console.WriteLine(vector); }            }}

或者:

 class Program   {   //......   static void Main(string[] args)  {   IEnumerable<Vector> vectors = GetVectors().ToArray();  foreach (var vector in vectors)  {   vector.X = 4; vector.Y = 4;  }  foreach (var vector in vectors)  {  Console.WriteLine(vector);  }              }  }

输出结果:

   1: X = 4, Y = 4
   2: X = 4, Y = 4
   3: X = 4, Y = 4

后记

其实本篇文章的意图并不在于yield这个关键字如何如何,因为不止是yield,我们一般的LINQ查询也会导致这个问题,而是借此说明IEnumerable对象和Array、List这样的集合类型的区别。IEnumerable这个接口和集合没有本质的联系,只是提供“枚举”的功能。甚至说,我们应该将IEnumerable对象当成“只读”的,如果我们需要“可写”的功能,你应该使用数组或者集合类型。至于本文提到的“延迟赋值”或者“延迟计算”,如果就“枚举”功能而言,也不是很准确,因为“枚举”不承诺“赋值”。

作者:Artech
出处:http://artech.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

转载于:https://www.cnblogs.com/hellolong/articles/2781046.html

从yield关键字看IEnumerable和Collection的区别相关推荐

  1. C#中使用的yield关键字是什么?

    在" 如何仅显示IList <>的片段"问题中,答案之一具有以下代码片段: IEnumerable<object> FilteredList() {fore ...

  2. 反编译使用yield关键字的方法

    我认为这是一个真命题:"没有用.NET Reflector反编译并阅读过代码的程序员不是专业的.NET程序员"..NET Reflector强大的地方就在于可以把IL代码反编译成可 ...

  3. c# yield关键字原理

    https://www.cnblogs.com/blueberryzzz/p/8678700.html c# yield关键字原理详解 1.yield实现的功能 yield return: 先看下面的 ...

  4. “ yield”关键字有什么作用?

    Python中yield关键字的用途是什么? 它有什么作用? 例如,我试图理解这段代码1 : def _get_child_candidates(self, distance, min_dist, m ...

  5. 人肉反编译使用yield关键字的方法

    我认为这是一个真命题:"没有用.NET Reflector反编译并阅读过代码的程序员不是专业的.NET程序员"..NET Reflector强大的地方就在于可以把IL代码反编译成可 ...

  6. python function if yield_Python中的yield关键字

    Python中的yield关键字 这是stackoverflow上一个关于yield关键字的问题以及它被推荐次数最高的一个答案 问题: Python中的yield关键字是什么?它是用来做什么的? 例如 ...

  7. 从range和xrange的性能对比到yield关键字(中)

    2019独角兽企业重金招聘Python工程师标准>>> 上节提出了range和xrange的效率问题,这节我们来探究其中的原因 yield的使用 我们看下面的程序: #coding: ...

  8. Python的yield关键字

    http://blog.csdn.net/tossgoer/archive/2010/08/18/5822303.aspx 忽然得知Python有个叫yield的关键字,好奇之下去查了查,花了点时间基 ...

  9. Python 生成器 和 yield 关键字

    Python 中 yield 的作用:http://youchen.me/2017/02/10/Python-What-does-yield-do/# Python 生成器详解:http://codi ...

  10. Mr.J -- yield关键字生成器产生值

    yield是什么 yield是ES6的新关键字,使生成器函数执行暂停,yield关键字后面的表达式的值返回给生成器的调用者.它可以被认为是一个基于生成器的版本的return关键字. yield关键字实 ...

最新文章

  1. 拒绝加班,办公电脑换新低至¥1999
  2. 语音识别:时间序列的Smith–Waterman对齐算法
  3. Python中如何修改字符串的值
  4. RHEL7恢复.bashrc文件
  5. js 求时间差 字符串转化为日期
  6. Google 的开源方法论
  7. php 内部异步执行顺序,event_loop中不同异步操作的执行顺序
  8. 在前端网页设计中 align 和 valign 两种对齐方式的不同取值区分(持续补充)
  9. 优化matlab作业,现代设计优化算法MATLAB实现
  10. OJ1045: 数值统计(c语言)
  11. 如何入门CTF夺旗赛
  12. Python3 异常: name ‘basestring‘ is not defined
  13. ThinkPHP 3 的输出
  14. 数据结构与算法python—1.数据结构与算法入门
  15. python实现天气功能查询
  16. Eclipse设置护眼(绿豆沙)颜色
  17. 批处理使用WinRAR压缩某类型的文件,一个文件压缩成一个压缩包,压缩后名称与原文件同名,压缩后删除原文件
  18. 密码的自动生成器:密码由大写字母/小写字母/数字组成,生成12位随机密码
  19. java 求指数、对数
  20. NLU(Natural Language Understanding)太难了

热门文章

  1. windows自动导出oracle数据库,Oracle数据库的自动导出备份脚本(windows环境)
  2. mysql安装包提示选项_Windows操作系统安装MySQL解压版
  3. Prototype使用$A()函数
  4. 给ApplicationContext容器中添加组件的方法(@Bean的使用)
  5. 【渝粤教育】国家开放大学2018年秋季 0248-21T电工电子技术 参考试题
  6. 【渝粤教育】广东开放大学 服务标准化 形成性考核 (41)
  7. 数据结构常见算法机试题
  8. Understand-4.0.877-Linux-64bit.tgz最新版本2017年源代码阅读利器,养眼theme之配置
  9. day3-python之函数初识(二)
  10. 搭建Hexo博客并部署到Github