迭代器
- 什么是迭代器?
1.1 C#中的迭代器
迭代器模式是设计模式中行为模式(behavioral pattern)的一个例子,他是一种简化对象间通讯的模式,也是一种非常容易理解和使用的模式。简单来说,迭代器模式使得你能够获取到序列中的所有元素而不用关心是其类型是array,list,linked list或者是其他什么序列结构。这一点使得能够非常高效的构建数据处理通道(data pipeline)--即数据能够进入处理通道,进行一系列的变换,或者过滤,然后得到结果。事实上,这正是LINQ的核心模式。
在.NET中,迭代器模式被IEnumerator和IEnumerable及其对应的泛型接口所封装。如果一个类实现了IEnumerable接口,那么就能够被迭代;调用GetEnumerator方法将返回IEnumerator接口的实现,它就是迭代器本身。迭代器类似数据库中的游标,他是数据序列中的一个位置记录。迭代器只能向前移动,同一数据序列中可以有多个迭代器同时对数据进行操作。
在C#1中已经内建了对迭代器的支持,那就是foreach语句。使得能够进行比for循环语句更直接和简单的对集合的迭代,编译器会将foreach编译来调用GetEnumerator和MoveNext方法以及Current属性,如果对象实现了IDisposable接口,在迭代完成之后会释放迭代器。但是在C#1中,实现一个迭代器是相对来说有点繁琐的操作。C#2使得这一工作变得大为简单,节省了实现迭代器的不少工作。
1.1.1 关于行为模式
行为模式关注系统中对象之间的相互交互,研究系统在运行时对象之间的相互通信和协作,进一步明确对象的职责,共11种模式。
一般包括:
模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式。
具体内容,见另一篇关于设计模式的文章
1.1.2 关于LINQ(不往.NET方向走,这个一般不重要)
Linq也就是Language Integrated Query的缩写,即语言集成查询,用来方便操作这些数据源
1.2 C++中的迭代器
要访问顺序容器和关联容器中的元素,需要通过“迭代器(iterator)”进行。迭代器是一个变量,相当于容器和操纵容器的算法之间的中介。迭代器可以指向容器中的某个元素,通过迭代器就可以读写它指向的元素。从这一点上看,迭代器和指针类似。
1.2.1 顺序容器和关联容器
1.2.2.顺序容器和关联容器的区别:
①顺序容器只有实值val。
②关联容器的一个元素包含两个部分:键值对(key-value) 即<k值(键值)|实值>。
③顺序容器不涉及排序,关联容器内部自动排序。
④本质区别:顺序容器通过元素在容器中的位置顺序存储和访问元素,而关联容器则是通过键(key)存储和读取元素的。
1.2.3.关联容器中的 有序容器和无序容器的区别:
①有序容器(底层结构是:红黑树)
是stl里的标准库。
②无序容器(底层结构是:散列表)
是boost库中的容器,目前boost库是准标准库,使用时需要添加库。
二、为什么要有迭代器?
总结来说:Iterator访问方式把对不同集合类的访问逻辑抽象出来,使得不用暴露集合内部的结构而达到循环遍历集合的效。
模板(C++的模板==C#中的容器)使得算法独立于存储的数据类型,而迭代器使算法独立于使用的容器类型。
三、如何使用迭代器,使用迭代器不同的方法比较
3.1 C#中使用迭代器的方法
3.1.0 先解释一下IEnumerable和IEnumerator不然看不懂代码
3.1.0.1 IEnumerable:
enum表示枚举,enumberable表示可枚举,I表示Interface接口,实现IEnumerable接口,表示这个类可以枚举。
内部值
只包含一个抽象的方法GetEnumerator().它返回一个IEnumerator枚举器对象。
3.1.0.2 IEnumerator:
枚举器,用来枚举集合中一个一个的项。
Current:获取集合中位于枚举数当前位置的元素。
MoveNext():游标的内部位置向前移动(就是移到一下个元素而已)
Reset():重置
3.1.1 C#传统使用迭代器的方法 (C#1)
3.1.1.1 具体代码实现:
using System;
using System.Collections;
namespace 迭代器Demo
{
class Program
{
//主函数入口
static void Main(string[] args)
{
//定义一个继承了IEnumerable接口的“朋友集合”
Friends friendcollection = new Friends();
//进行迭代遍历
foreach (Friend f in friendcollection)
{
Console.WriteLine(f.Name);
}
Console.Read();
}
}
/// <summary>
/// 朋友类
/// </summary>
public class Friend
{
private string name;
//定义Name的set和get
public string Name
{
get { return name; }
set { name = value; }
}
//构造函数
public Friend(string name)
{
this.name = name;
}
}
/// <summary>
/// 朋友集合
/// </summary>
public class Friends : IEnumerable
{
//朋友集合里面有哪些朋友
private Friend[] friendarray;
//构造函数
public Friends()
{
//初始化friendarray
friendarray = new Friend[]
{
new Friend("张三"),
new Friend("李四"),
new Friend("王五")
};
}
// 索引器 this[i]就可以返回friendarray中的某个friend
public Friend this[int index]
{
get { return friendarray[index]; }
}
public int Count
{
get { return friendarray.Length; }
}
// 实现IEnumerable<T>接口方法,IEnumberable接口主要是实现一个Get函数
public IEnumerator GetEnumerator()
{
return new FriendIterator(this);
}
}
/// <summary>
/// 自定义迭代器,必须实现 IEnumerator接口
/// </summary>
public class FriendIterator : IEnumerator
{
private readonly Friends friends;
private int index;
private Friend current;
internal FriendIterator(Friends friendcollection)
{
this.friends = friendcollection;
index = 0;
}
#region 实现IEnumerator接口中的方法
public object Current
{
get
{
return this.current;
}
}
public bool MoveNext()
{
if (index + 1 > friends.Count)
{
return false;
}
else
{
this.current = friends[index];
index++;
return true;
}
}
public void Reset()
{
index = 0;
}
#endregion
}
}
3.1.2 C#使用yield return的迭代器的方法(C#2)
C#2使得迭代变得更加简单:
public IEnumerator GetEnumerator()
{
for (int index = 0; index < this.values.Length; index++)
{
yield return values[(index + startingPoint) % values.Length];
}
}
一个需要执行的迭代块(yield block),他返回一个IEnumerator对象,你能够使用迭代块来执行迭代方法并返回一个IEnumerable需要实现的类型,IEnumerator或者对应的泛型。如果实现的是非泛型版本的接口,迭代块返的yield type是Object类型,否则返回的是相应的泛型类型。例如,如果方法实现IEnumerable<String>接口,那么yield返回的类型就是String类型。 在迭代块中除了yield return外,不允许出现普通的return语句。
编译器来为我们创建了一个状态机。当编译器遇到迭代块是,它创建了一个实现了状态机的内部类。这个类记住了我们迭代器的准确当前位置以及本地变量,包括参数。
当MoveNext被调用时,他需要执行GetEnumerator方法中的代码来准备下一个待返回的数据。
当调用Current属性是,需要返回yielded的值。
需要知道什么时候迭代结束是,MoveNext会返回false
下面来看看迭代器的执行顺序。
迭代器的执行流程
如下的代码,展示了迭代器的执行流程,代码输出(0,1,2,-1)然后终止。
class Program
{
static readonly String Padding = new String(' ', 30);
static IEnumerable<Int32> CreateEnumerable()
{
Console.WriteLine("{0} CreateEnumerable()方法开始", Padding);
for (int i = 0; i < 3; i++)
{
Console.WriteLine("{0}开始 yield {1}", i);
yield return i;
Console.WriteLine("{0}yield 结束", Padding);
}
Console.WriteLine("{0} Yielding最后一个值", Padding);
yield return -1;
Console.WriteLine("{0} CreateEnumerable()方法结束", Padding);
}
static void Main(string[] args)
{
IEnumerable<Int32> iterable = CreateEnumerable();
IEnumerator<Int32> iterator = iterable.GetEnumerator();
Console.WriteLine("开始迭代");
while (true)
{
Console.WriteLine("调用MoveNext方法……");
Boolean result = iterator.MoveNext();
Console.WriteLine("MoveNext方法返回的{0}", result);
if (!result)
{
break;
}
Console.WriteLine("获取当前值……");
Console.WriteLine("获取到的当前值为{0}", iterator.Current);
}
Console.ReadKey();
}
}
为了展示迭代的细节,以上代码使用了while循环,正常情况下一般使用foreach。和上次不同,这次在迭代方法中我们返回的是IEnumerable;对象而不是IEnumerator;对象。通常,为了实现IEnumerable接口,只需要返回IEnumerator对象即可;如果自是想从一个方法中返回一些列的数据,那么使用IEnumerable.
直到第一次调用MoveNext,CreateEnumerable中的方法才被调用。
在调用MoveNext的时候,已经做好了所有操作,返回Current属性并没有执行任何代码。
代码在yield return之后就停止执行,等待下一次调用MoveNext方法的时候继续执行。
在方法中可以有多个yield return语句。
在最后一个yield return执行完成后,代码并没有终止。调用MoveNext返回false使得方法结束。
第一点尤为重要:这意味着,不能在迭代块中写任何在方法调用时需要立即执行的代码--比如说参数验证。如果将参数验证放在迭代块中,那么他将不能够很好的起作用,这是经常会导致的错误的地方,而且这种错误不容易发现。
3.1.2.1如何停止迭代,以及finally语句块的特殊执行方式。
1.迭代到了100次,foreach自动执行Iterator. Dispose();
2.由于时间到了停止了迭代,yield break;
3.抛出了异常,finally语句总会执行
static IEnumerable<Int32> CountWithTimeLimit(DateTime limit)
{
try
{
for (int i = 1; i <= 100; i++)
{
if (DateTime.Now >= limit)
{
yield break;
}
yield return i;
}
}
finally
{
Console.WriteLine("停止迭代!"); Console.ReadKey();
}
}
static void Main(string[] args)
{
DateTime stop = DateTime.Now.AddSeconds(2);
foreach (Int32 i in CountWithTimeLimit(stop))
{
Console.WriteLine("返回 {0}", i);
Thread.Sleep(300);
}
}
4. 调用foreach等同于手动moveNext + Dispose
Foreach写法:
DateTime stop = DateTime.Now.AddSeconds(2);
foreach (Int32 i in CountWithTimeLimit(stop))
{
if (i > 3)
{
Console.WriteLine("返回中^");
return;
}
Thread.Sleep(300);
}
手动遍历的写法:
IEnumerable<Int32> iterable = CountWithTimeLimit(stop);
IEnumerator<Int32> iterator = iterable.GetEnumerator();
iterator.MoveNext();
Console.WriteLine("返回 {0}", iterator.Current);
iterator.MoveNext();
Console.WriteLine("返回 {0}", iterator.Current);
Console.ReadKey();
Iterator. Dispose(); //销毁并跳到finally
3.2 Lua中使用迭代器的方法
等待添加.....
3.3 C++中使用迭代器的方法
等待添加......
四、源码中如何实现迭代器,实现迭代器不同的方法比较
1.C#源码中的List.cs的主要参数
private const int _defaultCapacity = 4;
private T[] _items;
[ContractPublicPropertyName("Count")]
private int _size;
private int _version;
[NonSerialized]
private Object _syncRoot;
static readonly T[] _emptyArray = new T[0];
其中核心参数:
- _defauLtCapacity:
获取或设置该内部数据结构在不调整大小的情况下能够容纳的元素总数。
List是自动扩容的集合,在底层是使用Array作为存储结构的。List的默认存储数量是4。
当我们现有的存储数据的Array的长度不够的时候,就去new T[value]。
- _items
一个Array的存储结构。
- _size
存储结构的长度
- _version
//在遍历时如果发现_version变了立即退出并抛出遍历过程集合被修改异常,比如在foreach里remove或add元素就会导致这个异常。更常见的是出现在多线程时一个线程遍历集合,另一个线程修改集合的时候,相信很多人吃过苦头。
- _syncRoot
SyncRoot表示获取可用于同步 List 访问的对象
不论我们在代码的任何位置调用SyncRoot,返回的都是同一个object类型的对象.
通过lock (this._table.SyncRoot)这样的代码来实现线程安全的操作,其中this._table.SyncRoot返回的就是一个 object类型的对象,
我的理解:
不论有多少个地方新建了这个list的引用,为了线程安全,需要在操作前,对List进行上锁,而这个锁,就是syscRoot,一个object对象,这样添加了锁的信息,其他引用就能看到这个object的锁的信息
比如这种用法:
ListDictionary myCollection = new ListDictionary();
lock(myCollection.SyncRoot)
{
foreach (object item in myCollection)
{
// Insert your code here.
}
}
- C#源码中的List.cs中的主要迭代器方法:
2.1 GetEnumerator
public Enumerator GetEnumerator() {
return new Enumerator(this);
}
2.2 定义Enumerator的结构
public struct Enumerator : IEnumerator<T>, System.Collections.IEnumerator
{
private List<T> list;
private int index;
private int version;
private T current;
internal Enumerator(List<T> list) {
this.list = list;
index = 0;
version = list._version;
current = default(T);
}
#if !FEATURE_CORECLR
[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
#endif
public void Dispose() {
}
public bool MoveNext() {
List<T> 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);
}
}