您现在的位置是:首页 > 文章详情

C# yield关键字解析

日期:2019-04-07点击:433

相信好多程序员都是因为unity的协程(Coroutine)认识yield这个关键字的,知道在unity的开发中诸如yield return null、yield return new WaitForSeconds(1.0f)的用法,其实yield是C#的关键字,unity的协程只是在c#的基础上做了一层封装,我们现在来看看yield这个关键字。说到yield就不得不说迭代器,迭代器模式是设计模式的一种,因为其运用的普遍性,很多语言都有内嵌的原生支持。在.NET中,迭代器模式是通过IEnumerator、IEnumerable两个接口和两个同名的泛型接口来封装的:public interface IEnumerator { object Current { get; } bool MoveNext(); void Reset(); }IEnumerator只定义了一个属性、两个函数,Current为迭代器的当前值,通过调用MoveNext函数让迭代器的前进一步,返回值表示该迭代器是否结束,Reset函数用于重置数据。 public interface IEnumerable { IEnumerator GetEnumerator(); }IEnumerable更简单,返回迭代器。一般这两个接口的实现位于不同的类中。
foreach关键字之所以能方便对数组、List、Dictionary进行循环,其实也是在背后调用IEnumarator的MoveNext函数从头遍历到尾,取出每次的Current值,说白了它是个语法糖,在编译后会对我们的代码自动替换。我们来看下List的迭代器实现: public struct Enumerator : IEnumerator, System.Collections.IEnumerator { private List list; private int index; private int version; private T current; internal Enumerator(List list) { this.list = list; index = 0; version = list._version; current = default(T); } public void Dispose() { } public bool MoveNext() { List localList = list; if (version == localList._version && ((uint)index < (uint)localList._size)) { current = localList._items[index]; index++; return true; } return MoveNextRare(); } private bool MoveNextRare() { if (version != list._version) { ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion); } index = list._size + 1; current = default(T); return false; } public T Current { get { return current; } } Object System.Collections.IEnumerator.Current { get { if( index == 0 || index == list._size + 1) { ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen); } return Current; } } void System.Collections.IEnumerator.Reset() { if (version != list._version) { ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion); } index = 0; current = default(T); } }public class List : IEnumerable, ICollection, IList, ICollection, IEnumerable, IList{ IEnumerator IEnumerable.GetEnumerator() { return new Enumerator(this); }可以看到其实现是规规矩矩的继承了IEnumerator、IEnumerable及其两个泛型接口,一切都很完美,只有一个问题,是什么问题呢?答:写的太累了(手动滑稽)。终于引出了yield,没错,yield可以大大的简化迭代器代码,让Coder写起来更加轻松自在,我们的迭代代码可以这样写: public class Iteration: IEnumerable { public List lstInfo = new List() { 1, 3, 5, 7, 9, 11 }; public IEnumerator GetEnumerator() { for (int i = 0; i < lstInfo.Count; ++i) { yield return lstInfo[i]; } } }对于使用者来说方式还是一样: static void IterationTest() { Iteration obj = new Iteration(); foreach (var item in obj) { Console.WriteLine(item); } }当然啦,List的迭代器代码还是好多是关于版本号判断的,我们的示例并没有相关的逻辑,不过就算是加上,代码依然可以精简很多,这就是yield的魅力所在。有些人看到这可能还是迷惑,因为大部分的程序员的思路都是线性的,上面的Iteration类的GetEnumerator函数的for循环不是一下都遍历完了吗,怎么还能给foreach用,好蒙啊。。。yield很神奇吧?是这样的:Jon Skeet说:“迭代器模式的一个重要方面就是:不用一次返回所有数据,调用代码一次只需获取一个元素。”你可以理解为每次执行yield return都能够返回一个数据并暂停当前的状态,那暂停的状态什么时候会继续呢?在下一次调用到MoveNext的时候。什么时候会调用MoveNext?foreach执行完一次,进入下一次的时候。
如果还不是很明白,我们再来看看《c# in Depth》的经典例子:class IteratorWorkflow { static readonly string Padding = new string(' ', 30); static IEnumerable GetEnumerable() { Console.WriteLine("{0}Start of GetEnumerator()", Padding); for (int i = 0; i < 3; i++) { Console.WriteLine("{0}About to yield {1}", Padding, i); yield return i; Console.WriteLine("{0}After yield", Padding); } Console.WriteLine("{0}Yielding final value", Padding); yield return -1; Console.WriteLine("{0}End of GetEnumerator()", Padding); } public static void Main() { IEnumerable iterable = GetEnumerable(); IEnumerator iterator = iterable.GetEnumerator(); Console.WriteLine("Starting to iterate"); while (true) { Console.WriteLine("Calling MoveNext()..."); bool result = iterator.MoveNext(); Console.WriteLine("... MoveNext result={0}", result); if (!result) { break; } Console.WriteLine("Fetching Current..."); Console.WriteLine("... Current result={0}", iterator.Current); } } }输出的结果为: 我相信,如果你有对照着这个例子认真分析一遍的话,应该就能掌握yield这个知识点了,如果还不清楚,代码Copy下来,自己跑一遍~~
最后有几个知识点总结归纳一下:
1·在遇到yield break或者返回IEnumerator的函数体结束前,不管yield return 的值为多少,MoveNext都是会返回True。
2·在第一次调用MoveNext之前,返回IEnumerable的代码都不会执行,即使你有主动去调用它。
3·执行到yield return的地方,代码就暂停了,并返回相应的值,在下一次调用MoveNext时,从上次暂停的地方继续执行。
4·yield return 代码不能放入try...catch块中,但是能放入try...finally块中。
更多unity2018的功能介绍请到paws3d爪爪学院查找。

原文链接:https://yq.aliyun.com/articles/697158
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章