从c#迭代器到unity协程
先从c# 1.0的迭代器说起,这里主要借鉴《C# in depth》里对于迭代器内容的案例,并加以详细的说明
对一个对象要执行foreach遍历操作,这个对象必须继承Ienumbable接口,并实现GetIenumator方法,并且返回一个实现了IEnumerator接口的实例,
class Program { static void Main(string[] args) { object[] values = { "a", "b", "c", "d" }; IterationSample collection = new IterationSample(values, 1); foreach (var item in collection) { Console.WriteLine(item); } } } public class IterationSample : IEnumerable { object[] values; int startingPoint; // 构造函数 public IterationSample(object[] values, int startingPoint) { this.values = values; this.startingPoint = startingPoint; } // IEnumaerable接口实现 public IEnumerator GetEnumerator() { throw new NotImplementedException(); } }
接下来,我们使用c#1.0的方式实现这个GetEnumerator方法,来实现对IterationSample中数据的遍历,要实现GetEnumerator方法,需要注意以下两点
1.需要返回实现了IEnumerator接口的实例,我们采用创建内嵌类来实现,嵌套类型是可以访问它外层类型的私有成员, c# in depth中有解释为什么不在IterationSample去直接实现IEnumerator接口(也可以自行发散一下思维)
2.创建的内嵌类中需要存储指向“父级” (当前例子即指向IterationSample)以及当前遍历位置
下面声明IterationSampleIterator作为IterationSample的内嵌类累实现IEnumerator接口
public class IterationSampleIterator : IEnumerator { IterationSample parent; int position; internal IterationSampleIterator(IterationSample parent) { this.parent = parent; position = -1; //在第一个元素之前开始, }
//接口属性实现,返回当前值 public object Current { get { if (position == -1 || position == parent.values.Length) { throw new InvalidOperationException(); } int index = position + parent.startingPoint; index = index % parent.values.Length; return parent.values[index]; } }
//接口方法实现,每次要获取值之前,调用MoveNext方法,返回false则遍历结束 public bool MoveNext() { //如果需要遍历,则增加position值 if(position!=parent.values.Length) { position++; } //当不可遍历时,返回false return position < parent.values.Length; } public void Reset() { position = -1; } }
实现了迭代器类,接下里修改一下IterationSample中的GetIEnumerator方法,就可以实现对IterationSample类中的valuse数组进行遍历了
// IEnumaerable接口实现 public IEnumerator GetEnumerator() {
//返回迭代器类的实例,当我们使用foreach循环时,会自动调用迭代器类中的MoveNext方法并获取current,直到MoveNext返回false return new IterationSampleIterator(this); }
我们经常可能也会看见迭代的另一种写法
IEnumerable iterable = new IterationSample(values, 1); IEnumerator iterator = iterable.GetEnumerator(); while (iterator.MoveNext()) { Console.WriteLine(iterator.Current); }
正因为在c#1中实现迭代器很麻烦,所以在c#2中提供了yield关键字来简化迭代器的实现,在c#2中,我们只需几行简单的代码 就可以完全代替上面在IterationSample中实现的IterationSampleIterator内嵌类的功能,使用yield关键字改写GetIEnumator方法,只需两行代码
public IEnumerator GetEnumerator() { for (int i = 0; i < values.Length ; i++) yield return values[(i + startingPoint) % values.Length]; }
我们使用.Reflector反编译这段代码来查看编译器都做了哪些工作,进一步的理解yield语句,反编译后的代码如下
从上面的C#实现可以知道:函数内有多少个 yield return 在对应的 MoveNext() 就会返回多少次 true (不包含嵌套)。另外非常重要的一点的是:同一个函数内的其他代码(不是 yield return 语句)会被移到 MoveNext 中去。
有了yield关键字,我们还可以更方便的实现自定义的遍历函数,比如我们在IterationSample中添加一个MySort方法,返回类型为实现IEnumerable接口的实例,因为使用foreach,遍历的集合必须是实现IEnumerable接口的实例,因为有yield关键字,所以编译后Mysort方法中返回的实例对象即就是实现IEnumerable接口的父类实例
public IEnumerable MySort() { //我们设置只遍历前2个元素 for (int i = 0; i < values.Length-2; i++) yield return values[(i + startingPoint) % values.Length]; }
接下来使用我们自定义的方法来遍历
IterationSample collection = new IterationSample(values, 1); foreach (var item in collection.MySort() ) { Console.WriteLine(item); }
关于yield语句的一些使用注意事项,这里就不在讲,只说明yield语句背后的工作原理,可以自行了解,c# in depth中也有详细说明
接下里了解一下yield语句在unity协程中的使用
c# 2.0 yield关键字对迭代器的封装
反编译yield关键字实现的内容
unity协程的几种用法 及释义
嵌套线程的执行顺序