前言
我使用Unity时,发现Transform这个类可以使用foreach语句遍历其子对象,这是因为其在内部实现了一个枚举器。本文将阐述我关于实现枚举器的理解。
如何实现迭代器?
下方是Microsoft C#开发文档中关于IEnumerator的介绍,示例中为一个类实现了枚举器。
Microsoft Doc IEnumerator 接口对于这个示例,我的理解是,一个类要想实现枚举器,以使用foreach语句遍历这个非Generics类的集合,需要满足两个条件:
- 可以枚举(继承了
IEnumerable接口并实现其中的方法) - 制定了一个遍历的规则(有当前的对象,能跳转到下一个对象,能回到最初的位置),即继承了
IEnumerator接口并实现其成员
接下来我将进一步输出我的观点。在此之前,我想用自己的话解释UnityEngine.Transform是如何实现可以遍历子对象的Transform组件的枚举器的。
以下来自Unity官方的UnityCsReference仓库:UnityCsReference
public class Transform : Component, IEnumerable
{
public IEnumerator GetEnumerator()
{
return new Transform.Enumerator(this);
}
private class Enumerator : IEnumerator
{
Transform outer;
int currentIndex = -1;
internal Enumerator(Transform outer)
{
this.outer = outer;
}
//*undocumented*
public object Current
{
get { return outer.GetChild(currentIndex); }
}
//*undocumented*
public bool MoveNext()
{
int childCount = outer.childCount;
return ++currentIndex < childCount;
}
//*undocumented*
public void Reset() { currentIndex = -1; }
}
...
}
首先,Transform类继承了Component类和IEnumerable接口,并实现了接口中的方法。继承IEnumerable接口,说明其可以枚举!
然后,其内部实现了一个内部类Enumerator,这个类继承了IEnumerator接口。这说明它定义了枚举器遍历的规则。
内部类中,Transform outer;声明了一个Transform类型的变量,Enumerator的构造函数需要传入一个Transform类型变量,然后将传入的变量赋予outer。
在GetEnumerator()方法中,可以看到,调用了Enumerator的构造函数,传入的参数是Transform这个类的实例本身。说了这么多,意思就是内部类中outer指的就是Transform的一个实例本身。
currentIndex为当前索引,从-1开始,因为枚举器一开始就会调用MoveNext()方法,使得currentIndex变为0。
Current是当前对象,是一个属性,返回的是本Transform的一个实例的索引为currentIndex的子对象的Transform组件
MoveNext()方法中,先获取了当前Transform一个实例拥有的子对象数,再判断当前索引+1后,是否小于子对象总数,如果小于,说明可以继续往下遍历;反之不行,因为超出了索引范围。
Reset()方法能重置索引,当使用foreach遍历完后会自动调用该方法。
可能讲得不是很清楚,后续我会补充……