从yield关键字看IEnumerable和Collection的区别
C#的yield关键字由来以久,如果我没有记错的话,应该是在C# 2.0中被引入的。相信大家此关键字的用法已经了然于胸,很多人也了解yield背后的“延迟赋值”机制。但是即使你知道这个机制,你也很容易在不经意间掉入它制造的陷阱。
一、一个很简单的例子下面是一个很简单的例子:Vector为自定义表示二维向量的类型,Program的静态方法GetVetors方法获取以类型为IEnumerable<Vector> 表示的Vector列表,而方法通过yield关键字返回三个Vectior对象。在Main方法中,将GetVetors方法的返回值赋值给一个变量,然后对每一个Vector对象的X和Y进行重新赋值,最后将每一个Vector的信息输出来。从最后的输出我们不难看出,我们对Vector的重新赋值无效,最终的每一个Vector元素依旧“保持”着初始值。 class Program
{ static void Main(string[] args) { IEnumerable<Vector> vectors = GetVectors(); foreach (var vector in vectors) { vector.X = 4; vector.Y = 4; } ? Console.WriteLine(vector); } } { new Vector(2,3); } } double X { get; set; } public Vector(double x,double y) this.Y = y; ? string.Format("X = {0},Y = {1}",1)">this.X,1)">this.Y);
} 输出结果: 1: X = 1,Y = 1
3: X = 3,Y = 3 二、简单谈谈“延迟赋值”对于上面的现象,很多人一眼就可以看出这是由于yield背后的“延迟赋值”机制导致,但是不可否认我们会不经意间犯这种错误。为了让大家对这个问题有稍微深刻的认识,我们还是简单来谈谈“延迟赋值”。延迟赋值(Delay|Lazy Evaluation)又被称为延迟计算。为了避免不必要的计算导致的性能损失,和LINQ查询一样,yield关键字并不会导致后值语句的立即执行,而是转换成一个“表达式”。只有等到需要的那一刻(进行迭代)的时候,表达式被才被执行。 针对上面这个例子,我们对其进行简单的修改来验证“延迟赋值”的存在。我我们只需要在Vector的构造函数中添加一行语句:Console.WriteLine("Vector object is instantiated.");。从运行后的结过我们可以看出,Vector对象被创建了6次,来自于两次迭代。一次是对Vector元素的重新赋值,另一次源自对Vector元素的输出。由于两次迭代造作的并不是同一批对象,才会导致X和Y属性依然“保持”着原始的值。 3: //.....
5: { 7: 8: 9: } 1: Vector object is instantiated. 3: Vector object is instantiated. 5: X = 1,1)" id="lnum6"> 6: Vector object is instantiated. 8: Vector object is instantiated. internal private static IEnumerable<Vector> GetVectors() 5: new <GetVectors>d__0(-2);
7:? 9: { 11: foreach (Vector vector in vectors) 13: vector.X = 4.0; 15: } 18: Console.WriteLine(vector); 20: } 22:? 1: [CompilerGenerated] 3: { 5: private Vector <>2__current;
8: [DebuggerHidden] 10: bool MoveNext();
12: IEnumerator<Vector> IEnumerable<Vector>.GetEnumerator(); 14: IEnumerator IEnumerable.GetEnumerator(); 16: void IEnumerator.Reset();
18:? 20: object IEnumerator.Current { [DebuggerHidden] get; }
bool MoveNext() case 0: 7: this.<>2__current = new Vector(1.0,1.0); 9: true;
case 1: 14: this.<>1__state = 2;
17: case 2:
21: 23: case 3: 26: } 28: } //...... 8: { 10: vector.Y = 4; 12:? 15: Console.WriteLine(vector); 17: } 6: IEnumerable<Vector> vectors = GetVectors().ToArray(); 1: X = 4,Y = 4 3: X = 4,Y = 4 后记其实本篇文章的意图并不在于yield这个关键字如何如何,因为不止是yield,我们一般的LINQ查询也会导致这个问题,而是借此说明IEnumerable对象和Array、List这样的集合类型的区别。IEnumerable这个接口和集合没有本质的联系,只是提供“枚举”的功能。甚至说,我们应该将IEnumerable对象当成“只读”的,如果我们需要“可写”的功能,你应该使用数组或者集合类型。至于本文提到的“延迟赋值”或者“延迟计算”,如果就“枚举”功能而言,也不是很准确,因为“枚举”不承诺“赋值”。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- asp.net – OutOfMemoryException当发送大文件500MB使用Fil
- asp.net-mvc – 为什么这个路由参数被添加到查询字符串中?
- asp.net-mvc – 将Ninject与Udi Dahan的域事件一起使用
- 在ASP.NET核心中间件中设置响应状态
- entity-framework – 如何将OData查询与DTO映射到EF实体?
- asp.net-mvc – 为什么Nant不与TeamCity合作?
- asp.net-mvc – 为Azure Web角色定义缩放阈值
- asp.net-web-api – 在MVC 6中不可用的ResponseType
- asp.net-mvc – 未找到MVC 6 404
- asp.net-mvc – Basic Umbraco 6.1.1 SurfaceController问题
- asp.net-mvc – 在ASP.NET MVC中使用域对象和视图
- asp.net – 如何从Control继承而不是UserControl
- asp.net-mvc – ASP.NET MVC搜索路由
- asp.net – 什么是部分回发?
- asp.net-mvc – 使用RowAction更改Kendo MVC网格
- TagHelper是怎么实现的
- asp.net-mvc – 从Web窗体转换为MVC
- asp.net-mvc – 如何从umbraco mvc中的表面控制器
- asp.net-core – 如何在ConfigureServices中获取
- asp.net – Webservices可以作为单身人士引起不同