C# 基础小知识之yield 关键字 语法糖
原文地址:http://www.cnblogs.com/santian/p/4389675.html
对于yield关键字我们首先看一下msdn的解释:
如果你在语句中使用 yield 关键字,则意味着它在其中出现的方法、运算符或 get 访问器是迭代器。 通过使用 yield 定义迭代器,可在实现自定义集合类型的 IEnumerable 和 IEnumerator 模式时无需其他显式类(保留枚举状态的类,有关示例,请参阅 IEnumerator<T>)。
yield是一个语法糖
看msdn 的解释总是让人感觉生硬难懂。其实yield关键字很好理解。首先我们对于性质有个了解。yield是一个语法糖。既然yield是在C#中的一个语法糖,那么就说明yield是对一种复杂行为的简化,就是将一段代码简化为一种简单的形式,方便我们程序员使用。
那么yield到底是对什么行为的简化。我们首先来看一下yield的使用场景。
还是来看msdn上的例子。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{ foreach (int i in Power(2, 8, ""))
{
Console.Write("{0} ", i);
}
Console.ReadKey();
} public static IEnumerable<int> Power(int number, int exponent, string s)
{
int result = 1; for (int i = 0; i < exponent; i++)
{
result = result * number;
yield return result;
}
yield return 3;
yield return 4;
yield return 5;
} }
}

这是msdn上yield的一种使用场景。
我们首先看一下下面的Power方法。该静态方法返回一个IEnumerablel<int>类型的参数。按照我们平常的做法。应该对数据执行一定操作,然后return一个IEnumerablel<int>类型的参数。我们把Power方法改造如下:

public static IEnumerable<int> Power(int number, int exponent, string s)
{
int result = 1;
//接口不能实例化,我们这儿new一个实现了IEnumerable接口的List
IEnumerable<int> example = new List<int>();
for (int i = 0; i < exponent; i++)
{
result = result * number;
(example as List<int>).Add(result);
}
return example;
}

这是我们平常的思路。但是这样做就有个问题。这儿要new一个List,或者任何实现了IEnumerable接口的类型。这样也太麻烦了吧。要知道IEnumerable是一个常用的返回类型。每次使用都要new一个LIst,或者其他实现了该接口的类型。与其使用其他类型,不如我们自己定制一个实现了IEnumerable接口专门用来返回IEnumerable类型的类型。我们自己定制也很麻烦。所以微软帮我们定制好了。这个类是什么,那就是yield关键字这个语法糖。
语法糖的实现(实现IEnumerable<T>接口的类)
我们来看一下yield的反编译代码。

namespace ConsoleApplication2
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices; internal class Program
{
private static void Main(string[] args)
{
IEnumerable<int> enumerable = Power(2, 8);
Console.WriteLine("Begin to iterate the collection.");
foreach (int num in Power(2, 8))
{
Console.Write("{0} ", num);
}
Console.ReadKey();
} public static IEnumerable<int> Power(int number, int exponent)
{
<Power>d__0 d__ = new <Power>d__0(-2);
d__.<>3__number = number;
d__.<>3__exponent = exponent;
return d__;
} [CompilerGenerated]
private sealed class <Power>d__0 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable
{
private int <>1__state;
private int <>2__current;
public int <>3__exponent;
public int <>3__number;
private int <>l__initialThreadId;
public int <result>5__1;
public int exponent;
public int number; [DebuggerHidden]
public <Power>d__0(int <>1__state)
{
this.<>1__state = <>1__state;
this.<>l__initialThreadId = Environment.CurrentManagedThreadId;
} private bool MoveNext()
{
switch (this.<>1__state)
{
case 0:
this.<>1__state = -1;
this.<result>5__1 = 1;
Console.WriteLine("Begin to invoke GetItems() method");
this.<>2__current = 3;
this.<>1__state = 1;
return true; case 1:
this.<>1__state = -1;
this.<>2__current = 4;
this.<>1__state = 2;
return true; case 2:
this.<>1__state = -1;
this.<>2__current = 5;
this.<>1__state = 3;
return true; case 3:
this.<>1__state = -1;
break;
}
return false;
} [DebuggerHidden]
IEnumerator<int> IEnumerable<int>.GetEnumerator()
{
Program.<Power>d__0 d__;
if ((Environment.CurrentManagedThreadId == this.<>l__initialThreadId) && (this.<>1__state == -2))
{
this.<>1__state = 0;
d__ = this;
}
else
{
d__ = new Program.<Power>d__0(0);
}
d__.number = this.<>3__number;
d__.exponent = this.<>3__exponent;
return d__;
} [DebuggerHidden]
IEnumerator IEnumerable.GetEnumerator()
{
return this.System.Collections.Generic.IEnumerable<System.Int32>.GetEnumerator();
} [DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
} void IDisposable.Dispose()
{
} int IEnumerator<int>.Current
{
[DebuggerHidden]
get
{
return this.<>2__current;
}
} object IEnumerator.Current
{
[DebuggerHidden]
get
{
return this.<>2__current;
}
}
}
}
}

反编译代码有三部分,其中程序的入口点 private static void Main(string[] args) Power方法 public static IEnumerable<int> Power(int number, int exponent) 和我们自己写的代码一样,但是反编译代码中还多了一个密封类
private sealed class <Power>d__0 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable
现在情况已经明了了。yield这个语法糖实现了一个实现 IEnumerable<int>接口的类来返回我们需要到 IEnumerable<int>类型的数据。
我们再看一下反编译后的Power方法

public static IEnumerable<int> Power(int number, int exponent)
{
<Power>d__0 d__ = new <Power>d__0(-2);
d__.<>3__number = number;
d__.<>3__exponent = exponent;
return d__;
}

此时就确认,的确是使用了实现枚举接口的类来返回我们需要的数据类型。
每次yield return <expression>;就会像该类的实例中添加 一条数据。当yield break;的时候停止添加。
至此yield的用法就很清楚了。当我们需要返回IEnumerable类型的时候,直接yield返回数据就可以了。也不用new一个list,或其他类型。所以yield是一个典型的语法糖。
yield使用中的特殊情况
我们看到编译器将我们yield的数据添加到了一个集合中。Power方法在编译器中实例化了一个实现枚举接口的类型。但是我们在Power方法中写一些方法,编译器会如何处理

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
//这儿调用了方法。
var test = Power(2, 8, "");
Console.WriteLine("Begin to iterate the collection.");
//Display powers of 2 up to the exponent of 8:
foreach (int i in Power(2, 8, ""))
{
Console.Write("{0} ", i);
}
Console.ReadKey();
}
public static IEnumerable<int> Power(int number, int exponent, string s)
{
int result = 1;
if (string.IsNullOrEmpty(s))
{
//throw new Exception("这是一个异常");
Console.WriteLine("Begin to invoke GetItems() method");
} for (int i = 0; i < exponent; i++)
{
result = result * number;
yield return result;
}
yield return 3;
yield return 4;
yield return 5;
}
}
}

按照我们的理解当我们 var test = Power(2, 8, "");的时候确实调用了Power方法。此时应该程序打印Console.WriteLine("Begin to invoke GetItems() method");然后继续执行 Console.WriteLine("Begin to iterate the collection.");方法。所以打印顺序应该是
Begin to invoke GetItems() method
Begin to iterate the collection.
但是我们运行的时候却发现
打印顺序和我们想象的不同。此时还是去看反编译代码。

namespace ConsoleApplication2
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices; internal class Program
{
private static void Main(string[] args)
{
IEnumerable<int> enumerable = Power(2, 8, "");
Console.WriteLine("Begin to iterate the collection.");
foreach (int num in Power(2, 8, ""))
{
Console.Write("{0} ", num);
}
Console.ReadKey();
} public static IEnumerable<int> Power(int number, int exponent, string s)
{
<Power>d__0 d__ = new <Power>d__0(-2);
d__.<>3__number = number;
d__.<>3__exponent = exponent;
d__.<>3__s = s;
return d__;
} [CompilerGenerated]
private sealed class <Power>d__0 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable
{
private int <>1__state;
private int <>2__current;
public int <>3__exponent;
public int <>3__number;
public string <>3__s;
private int <>l__initialThreadId;
public int <i>5__2;
public int <result>5__1;
public int exponent;
public int number;
public string s; [DebuggerHidden]
public <Power>d__0(int <>1__state)
{
this.<>1__state = <>1__state;
this.<>l__initialThreadId = Environment.CurrentManagedThreadId;
} private bool MoveNext()
{
switch (this.<>1__state)
{
case 0:
this.<>1__state = -1;
this.<result>5__1 = 1;
if (string.IsNullOrEmpty(this.s))
{
Console.WriteLine("Begin to invoke GetItems() method");
}
this.<i>5__2 = 0;
while (this.<i>5__2 < this.exponent)
{
this.<result>5__1 *= this.number;
this.<>2__current = this.<result>5__1;
this.<>1__state = 1;
return true;
Label_009D:
this.<>1__state = -1;
this.<i>5__2++;
}
this.<>2__current = 3;
this.<>1__state = 2;
return true; case 1:
goto Label_009D; case 2:
this.<>1__state = -1;
this.<>2__current = 4;
this.<>1__state = 3;
return true; case 3:
this.<>1__state = -1;
this.<>2__current = 5;
this.<>1__state = 4;
return true; case 4:
this.<>1__state = -1;
break;
}
return false;
} [DebuggerHidden]
IEnumerator<int> IEnumerable<int>.GetEnumerator()
{
Program.<Power>d__0 d__;
if ((Environment.CurrentManagedThreadId == this.<>l__initialThreadId) && (this.<>1__state == -2))
{
this.<>1__state = 0;
d__ = this;
}
else
{
d__ = new Program.<Power>d__0(0);
}
d__.number = this.<>3__number;
d__.exponent = this.<>3__exponent;
d__.s = this.<>3__s;
return d__;
} [DebuggerHidden]
IEnumerator IEnumerable.GetEnumerator()
{
return this.System.Collections.Generic.IEnumerable<System.Int32>.GetEnumerator();
} [DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
} void IDisposable.Dispose()
{
} int IEnumerator<int>.Current
{
[DebuggerHidden]
get
{
return this.<>2__current;
}
} object IEnumerator.Current
{
[DebuggerHidden]
get
{
return this.<>2__current;
}
}
}
}
}

我们看到Power方法

public static IEnumerable<int> Power(int number, int exponent, string s)
{
<Power>d__0 d__ = new <Power>d__0(-2);
d__.<>3__number = number;
d__.<>3__exponent = exponent;
d__.<>3__s = s;
return d__;
}

还是还我们没有加打印方法之前一样。我们的打印方法并没有出现在Power方法中,而是被封装进了实现枚举接口的类方法 private bool MoveNext()中。所以方法不会立即被执行,而是在我们使用数据的时候被执行。如果对此机制不了解,就容易出现另外一些意想不到的问题。例如在Power方法中添加一些验证程序,如果不符合条件就抛出一个异常。这样的异常检查不会被执行。只有我们使用数据的时候才会执行。这样就失去了检查数据的意义。
具体的例子可以看Artech博主的文章
另外使用yield还有一些注意事项:
你不能在具有以下特点的方法中包含 yield return 或 yield break 语句:
匿名方法。 有关详细信息,请参阅匿名方法(C# 编程指南)。
包含不安全的块的方法。 有关详细信息,请参阅unsafe(C# 参考)。
异常处理
不能将 yield return 语句置于 try-catch 块中。 可将 yield return 语句置于 try-finally 语句的 try 块中。
yield break 语句可以位于 try 块或 catch 块,但不能位于 finally 块。
如果 foreach 主体(在迭代器方法之外)引发异常,则将执行迭代器方法中的 finally 块。
本文地址:http://www.cnblogs.com/santian/p/4389675.html
博客地址:一天两天三天
C# 基础小知识之yield 关键字 语法糖的更多相关文章
- C# 基础小知识之yield 关键字
对于yield关键字我们首先看一下msdn的解释: 如果你在语句中使用 yield 关键字,则意味着它在其中出现的方法.运算符或 get 访问器是迭代器. 通过使用 yield 定义迭代器,可在实现自 ...
- Java基础小知识(一)
序言:“不积跬步,无以至千里.不积小流,无以成江海.”每一个庞大的知识架构都是由无数个小知识点慢慢积累起来的,没有量变的积累,就没有质变上的飞跃,成功往往离不开积累. 今天就和大家分享一些我在学习J ...
- 支付宝小程序与微信小程序开发功能和语法糖不同
最近开始负责公司webapp数据打通支付宝小程序,之前已经打通了微信小程序,现在根据支付宝小程序的开发文档在之前的模板上面做修改. 在修改模板的过程中,总结一下双方功能和语法糖的不同之处. 框架: a ...
- 刚开始学习Javascript的一些基础小知识,从入门到崩溃,希望对大家有帮助(只适合初学者)
一.简介 1.JavaScript一种直译式脚本语言,是一种动态类型.弱类型.基于原型的语言,内置支持类型,js不能操作文件. 重要的开始啦!!!!! 引入javascript: 行间js <d ...
- Java基础小知识笔记
1. Integer转进制的一个类2. toBinaryString,toOctalString,toHexString.(转为二进制,八进制,十六进制的方法)3. 如果·数据的大小没有超过byte/ ...
- 学习笔记:Java的一些基础小知识之JVM与GC
一.JVM是什么 Java虚拟机(英语:Java Virtual Machine,缩写为JVM),又名爪哇虚拟器,一种能够运行Java bytecode的虚拟机,以堆栈结构机器来进行实做.最早由太 ...
- java基础小知识
1.1常量: 基本数据类型常量 字符常量 整数常量的表现形式:一进制的形式来表示(二进制,八进制,十进制,十六进制) 生活中:十进制(0-9) ,星期(七进制(0-6)) ,时间(十二进制(0-11 ...
- Java的一些基础小知识之JVM与GC (转)
一.JVM是什么 Java虚拟机(英语:Java Virtual Machine,缩写为JVM),又名爪哇虚拟器,一种能够运行Java bytecode的虚拟机,以堆栈结构机器来进行实做.最早由太阳微 ...
- java线程的一些基础小知识
--------------------------------------------------------------------------------------------------线程 ...
随机推荐
- SQL SERVER 查看死锁的存储过程
end
- Asp与Asp.Net的区别
今天在网上看到一位朋友问asp与asp.net的区别.编辑本人也是从asp转型到.net来的,几年了,几乎都忘记了asp的存在,也说不出它们之间的区别,因为感觉两者是根本就没有联系,非要说有联系,那就 ...
- 【转】jQuery教程
“jQuery风暴” 推荐及配套代码下载 ziqiu.zhang 2011-03-24 00:28 阅读:15339 评论:100 从零开始学习jQuery(剧场版) 你必须知道的javascri ...
- Wdcp两日志的路径
Wdcp两日志的路径: /www/wdlinux/httpd-2.2.22/logs /www/wdlinux/nginx-1.0.15/logs
- 使用$.getJSON实现跨域ajax请求
jQuery中常用getJSON来调用并获取远程的JSON字符串,将其转换为JSON对象,如果成功,则执行回调函数.原型如下: jQuery.getJSON( url, [data], [callba ...
- YII session存储 调用login方法
当要进行用户的session存储的时候,可以调用里面的login方法进行存储
- Python自动化运维之22、JavaScript
一.简介 JavaScript是一门编程语言,浏览器内置了JavaScript语言的解释器,所以在浏览器上按照JavaScript语言的规则编写相应代码之,浏览器可以解释并做出相应的处理.学习了htm ...
- Josephus2
约瑟夫问题升级问题 编号为1~N的N个人按顺时针方向围坐一圈,每个人持有一个密码(正整数,可以自由输入),开始人选一个正整数作为报数上限值M,从第一个人按顺时针方向自1开始顺序报数,报道M是停止报数. ...
- SQL联合索引 与 单一列的索引
SQL联合索引 与 单一列的索引 标签: sqlwebobjectstatistics优化磁盘 2012-06-12 13:46 27992人阅读 评论(1) 收藏 举报 分类: 数据库(94) ...
- 从一到二:利用mnist训练集生成的caffemodel对mnist测试集与自己手写的数字进行测试
通过从零到一的教程,我们已经得到了通过mnist训练集生成的caffemodel,主要包含下面四个文件: 接下来就可以利用模型进行测试了.关于测试方法按照上篇教程还是选择bat文件,当然python. ...