前言
在正文开始之前先吐槽一下,我发现出来混真的迟早都是要还的。直接上图:
- foreach原理 底层原理没细究
- 学习游戏开发-> Unity->协程->迭代器 底层原理吓跑我
- 学习游戏开发->设计模式->迭代器模式 来吧,这次不跑了
迭代器
什么场景下需要使用迭代器?
假设有一个数据容器(可能是Array List Tree等),对使用者来说,显然希望这个数据容器能够提供一种无须了解它的内部实现就可以获取其元素的方法,无论它是Array还是List,都希望可以通过相同的方法达到我们的目的。
迭代器模式它通过持有迭代状态追踪当前元素,并且识别下一个需要被迭代的元素,从而可让用户通过使用特定的界面巡防容器中的每一个元素而无需了解底层的实现。
在C#中,迭代器被封装在
IEnumerable IEnumerator IEnumerable IEnumerator
IEnumerable非泛型形式
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
IEnumerator非泛型形式
public interface IEnumerator
{
Object Current {get;}
bool MoveNext();
void Reset();
}
IEnumerable泛型形式
public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
IEnumerator GetEnumerator();
}
IEnumerator泛型形式
public interface IEnumerator<out T> : IDisposable, IEnumerator
{
void Dispose();
Object Current {get;}
T Current{get;}
bool MoveNext();
}
public interface IDisposable
{
void Dispose();
}
IEnumerable
接口定义了一个可以获取IEnumerator
的方法——GetEnumerator
。而在IEnumerator
则在目标序列上实现循环迭代(使用MoveNext()
方法,以及Current
属性来实现),直到你不再需要任何数据或者没有数据可以被返回。使用这个接口,可以保证我们实现常见的foreach
循环。
Q :
为什么会有2个接口呢?为何IEnumerable
自己不直接实现MoveNext()
方法,提供Current
属性,而是使用额外的一个接口IEnumerator
来专门处理?
A :
假设有两个不同的迭代器要对同一个序列进行迭代,则需要保证这两个独立的迭代状态能够被正确地保存和处理。这也是IEnumerator
要做的。而为了不违背单一职责原则,不使IEnumerable
拥有过多职责从而陷入分工不明的窘境,所以IEnumerable
自己并没有实现MoveNext()
方法。
迭代器的执行步骤
using System;
using System.Collections.Generic;
namespace Iterator
{
class Program
{
static void Main(string[] args)
{
foreach (string s in GetEnumerableTest() )
{
Console.WriteLine(s);
}
}
static IEnumerable<string> GetEnumerableTest()
{
yield return "begin";
for (int i = 0; i < 10; i++)
{
yield return i.ToString();
}
yield return "end";
}
}
}
输出结果:
上面代码的执行过程:
Main
调用GetEnmuerableTest()
方法。GetEnmuerableTest()
方法会为我们创建一个编译器生成的新的类“Iterator/‘<GetEnmuerableTest()>c_Iterator’”的实例。此时GetEnmuerableTest()
方法中的代码尚未执行。Main
调用MoveNext()
方法。- 迭代器开始执行,直到它遇到第一个
yield
return
语句。此时迭代器会获取当前的值是“begin”并且返回true以告知此时还有数据。 Main
使用Current
属性以获取数据,并打印出来。Main
再次调用MoveNext()
方法。- 迭代器继续从上次遇到
yield
return
的地方开始执行,并且和之前一样,直到遇到下一个yield
return
语句。 - 迭代器按照这种方式循环,直到
MoveNext()
方法返回false,以告知此时已经没有数据了。
Hints:
- 在第一次调用
MoveNext()
方法之前,我们自己在GetEnmuerableTest()
中的代码不会执行。 - 之后调用
MoveNext()
方法时,会从上次暂停(yield
return
)的地方开始。 - 编译器会保证
GetEnmuerableTest()
方法中的局部变量能够被保留,换句话说,虽然本例中的i
是值类型实例,但是它的值其实是被迭代器保存在堆上的,这样才能保证每次调用MoveNext()
时,它是可用的。这也是说明迭代器块中的局部变量会被分配在堆上的原因。
依靠状态机实现迭代器
foreach
foreach( var item in list)
{
Console.WriteLine(item);
}
var enumerator = list.GetEnumerator();
while(enumerator.MoveNext() )
{
Console.WriteLine(enumerator.Current);
}
注意foreach的效率比for更高;
迭代器模式
提供一种方法访问一个容器对象中各个元素,而又不需暴露该对象的内部细节。
迭代类:
- 读写分离;
- 封装了元数据:比如底层的array数组;
- 简化的业务逻辑;
- 迭代器简化了聚合类。
C#迭代器模式Demo
using System;
using System.Collections.Generic;
namespace IteratorPattern
{
class Program
{
static void Main(string[] args)
{
Aggreation aggreation = new Aggreation();
aggreation.Add(0);
aggreation.Add(10);
aggreation.Add(12);
var enumerator = aggreation.GetEnumerable();
while (enumerator.MoveNext())
{
Console.WriteLine(enumerator.Current);
}
}
}
public interface IMyEnumerable
{
int Current { get; }
bool MoveNext();
}
public class MyEnumerable : IMyEnumerable
{
public int Current { get; set; }
private int index = 0;
private int current = 0;
private Aggreation aggreation = new Aggreation();
public MyEnumerable(Aggreation aggreation)
{
this.aggreation = aggreation;
}
public bool MoveNext()
{
if(index < aggreation.length)
{
this.Current = aggreation[index];
index++;
return true;
}
return false;
}
}
public class Aggreation
{
private List<int> list = new List<int>();
public MyEnumerable GetEnumerable()
{
return new MyEnumerable(this);
}
public void Add(int num)
{
list.Add(num);
}
public int this[int index]
{
get
{
return list[index];
}
}
public int length
{
get { return list.Count; }
}
}
}
测试结果:
参考资料
- Unity3D脚本编程 陈嘉栋 著
- https://www.bilibili.com/video/av78515440?p=4
- 更多: