这篇开始我们会对C#中的迭代器进行详细的讲解。
首先对于C#中用到迭代器的地方都是在数据结构中将迭代逻辑封装起来。使我们可以在不需要关心数据结构的内部数据存储结构的情况下顺利的遍历到每一个数据。
List<int> nums = new List<int>();
for(int i = 0;i< nums.Count; i++)
{
Debug.Log(nums[i]);
}
看到上述代码,我们使用for循环,对索引进行递增操作,然后根据索引去获取数组中的每一个元素。每一次循环i都会+1,所以我们可以依次获取所有的元素,那如果我们将i++换成i+=2,那么即是获取索引为偶数的元素。那么这个也就是我们进行迭代的逻辑,我们迭代的逻辑是在外部定义的。因为我们知道list数组的索引是从0开始一次递增的。所以我们可以这么写,那如果像字典dictionary呢?我们并不清楚他的存储结构,并不知道他的所有key是多少。所以我们迭代器的作用就是将迭代逻辑放在数据结构当中。
class MyList {
List<int> nums;
public MyList()
{
nums = new List<int>();
}
public void Add(int num)
{
nums.Add(num);
}
private int index = 0;
public int Current => nums[index];
public bool MoveNext()
{
index++;
if (index < nums.Count)
return true;
Reset();
return false;
}
public void Reset() => index = 0;
}
MyList nums = new MyList();
nums.Add(1);
nums.Add(2);
do
{
Debug.Log(nums.Current);
} while (nums.MoveNext());
我们使用do while循环,一直去调用MoveNext函数,推进index索引,当index未出界,则返回true,也就是还未遍历完,遍历完了就返回false,所以,我们在循环中一直访问的Current,则会获取到该数组的每一个元素。
如此一来,我们就将该数据结构的迭代逻辑放在了数据结构里面了,外部不需要知道我们的存储结构,他只管调用movenext,然后访问current即可。所以我们不需要关心他的数据存储方式。
那么这么做其实是有问题的。举个例子。
MyList nums = new MyList();
nums.Add(1);
nums.Add(2);
do
{
do
{
Debug.Log(nums.Current);
}
while (nums.MoveNext());
} while (nums.MoveNext());
如果我这么写会有什么后果?死循环!!!
因为这是同一个对象,因此他的index实际上是同一个,那么就会导致,每次内层while结束,index都会被置为0,所以外层while永远不会结束。
所以我们应该将迭代逻辑抽出来。
class MyList {
List<int> nums;
public MyList()
{
nums = new List<int>();
}
public void Add(int num)
{
nums.Add(num);
}
public MyIter GetIter()
{
return new MyIter(nums);
}
}
class MyIter
{
List<int> nums;
public MyIter(List<int> nums)
{
this.nums = nums;
}
private int index = 0;
public int Current => nums[index];
public bool MoveNext()
{
index++;
if (index < nums.Count)
return true;
Reset();
return false;
}
public void Reset() => index = 0;
}
MyList nums = new MyList();
nums.Add(1);
nums.Add(2);
MyIter iter1 = nums.GetIter();
MyIter iter2 = nums.GetIter();
do
{
do
{
Debug.Log(iter2.Current);
}
while (iter2.MoveNext());
} while (iter1.MoveNext());
我们每次进行遍历的时候都通过数据结构的GetIter方法获取一个新的迭代器。使每次遍历的迭代索引和逻辑相互独立,则不会造成影响。
这是为了方便大家理解所写的一个非常简陋的迭代器。为的是让大家可以更好的理解我们C#中自带的迭代器。即两个接口。IEnumerable和IEnumerator。
我直接上代码。大家对比下上面的代码相信直接就可以看懂了。
class MyList :IEnumerable{
List<int> nums;
public MyList()
{
nums = new List<int>();
}
public void Add(int num)
{
nums.Add(num);
}
public IEnumerator GetEnumerator()
{
return new MyIter(nums);
}
}
我们需要支持foreach遍历的迭代器要继承IEnumerable该类,并实现他的GetEnumerator方法,即是获取迭代器的方法,也就是上面的GetIter。
class MyIter:IEnumerator
{
List<int> nums;
public MyIter(List<int> nums)
{
this.nums = nums;
}
private int index = 0;
object IEnumerator.Current => nums[index];
public bool MoveNext()
{
index++;
if (index < nums.Count)
return true;
return false;
}
public void Reset() => index = 0;
}
而我们的迭代器类需要继承IEnumerator,并实现Current,movenext和reset三个方法。意义上面已经阐述过了。那么继承这两个接口的好处是,可以使用foreach。
MyList nums = new MyList();
nums.Add(1);
nums.Add(2);
foreach(int num in nums)
{
Debug.Log(num);
}
在foreach开始时,会自动的去调用我们数据结构的GetEnumerator方法获取一个新的迭代器,然后每次循环都是去调用Movenext方法推进索引,当movenext返回false则循环结束。
到这里相信大家都明白了迭代器是怎么一回事了。
PS:字典是可以用for循环进行遍历的。可以将字典的所有key存在一个数据中,在for循环中依次根据key去获取对应的值即可。