定义
微软定义:如果你在语句中使用 yield 关键字,则意味着它在其中出现的方法、运算符或 get 访问器是迭代器。 通过使用 yield 定义迭代器,可在实现自定义集合类型的 IEnumerable 和 IEnumerator 模式时无需其他显式类(保留枚举状态的类,有关示例,请参阅 IEnumerator<T>)。
概念
yield关键字是微软推出的一个语法糖,使用它可以使代码具有更高可读性和更好性能。当我们需要返回IEnumerable类型的对象的时候,直接yield返回数据就可以了。也不用new一个list,或其他类型。
代码
获取10以内的能整除2的数,因为方法定义的是List<int>类型,按照常规做法会在方法内定义一个List<int>类型然后返回,但这样有一个问题,如果循环代码块内部某块代码特别耗时,会造成等所有循环都结束才能获取返回的值,这样用户无法了解程序内部的运行情况,这里通过Thread.Sleep(1000)的方式模拟延迟场景,最后运行,发现需要等待11秒左右才能看到所有满足条件的数据。
List<int> GetNum1(int count) { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); List<int> result = new List<int>(); for (int i = 0; i <= count; i++) { if (i % 2 == 0) { result.Add(i); } Thread.Sleep(1000); } stopwatch.Stop(); Console.WriteLine($"耗时:{stopwatch.ElapsedMilliseconds}毫秒"); return result; }
static void Main(string[] args) { IEnumerable<int> nums = GetNum1(10); foreach (var n in nums) { Console.Write("{0} ", n); } Console.ReadLine(); }
在上面代码的基础上,通过使用yield关键字进行改进
IEnumerable<int> GetNum2(int count) { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); for (int i = 0; i <= count; i++) { if (i % 2 == 0) { yield return i; } Thread.Sleep(1000); } stopwatch.Stop(); Console.WriteLine($"耗时:{stopwatch.ElapsedMilliseconds}毫秒"); }
static void Main(string[] args) { IEnumerable<int> nums = GetNum2(10); foreach (var n in nums) { Console.Write("{0} ", n); } Console.ReadLine(); }
调试运行发现,同样总耗时时间是11秒左右,但是与之前方式相比,使用yield关键字,会每隔2秒输出一个数字,直至数字全部输出完,两种方式虽然等待时间相同,但相较于前者,后者用户体验更好。
使用场景
把指令推迟到程序实际需要的时候再执行,这个特性允许我们更细致地控制集合每个元素产生的时机。
优点
1. 可以像上面演示的那样尽可能即时地给用户响应。
2. 可以提高内存使用效率。当我们有一个方法要返回一个集合时,而作为方法的实现者我们并不清楚方法调用者具体在什么时候要使用该集合数据。如果我们不使用 yield
关键字,则意味着需要把集合数据装载到内存中等待被使用,这可能导致数据在内存中占用较长的时间。
迭代器
包含 yield
语句并返回 IEnumerable<T>
类型的方法称为迭代器。
迭代器与普通方法之间的区别
迭代器方法和普通的方法相比,普通方法是通过 return
语句立即把程序的控制权交回给调用者,同时也会把方法内的本地(局部)资源释放掉。迭代器方法则是在依次返回多个值给调用者的期间保留本地资源,等所有值都返回结束时再释放掉本地资源,这些返回的值将形成一组序列被调用者使用。
yield语句的两种形式
yield return <expression>:返回集合的一个元素,把程序控制权交回调用者并保留本地状态,调用者拿到返回的值继续往后执行。
yield break:终止,相当于正常代码块的 return
语句(迭代器中直接使用 return
是非法的)。
yield使用注意事项
1. yield所在方法的返回类型必须是IEnumerable、IEnumerable<T>、IEnumerator、IEnumerator<T>其中的一种。
2. 不能有任何ref或out参数。
3. 不能将yield return 语句放在 try-catch 语句块中,但可以放在 try-finally 语句的try块中。
4. 不能将yield break 语句放在finally块中,但可以放在 try 块或者 catch 块中。