转自: http://www.cnblogs.com/bluefee
[翻译+补充说明]
原文参考:http://www.theserverside.net/articles/showarticle.tss?id=IteratorsWithC2
注释:对于Enumerable和Enumerator的翻译可能不太贴切
可以参考以下词汇:枚举 枚举构造器
如果一个类实现了System.Collections.IEnumerable接口那么它就是一个枚举器。这里是一些接口的定义:
{
//为接口绑定属性
[System.Runtime.InteropServices.GuidAttribute(“496B0ABE-CDEE-11d3-88E8-00902754C43A”)]
//注意这里是为了运行时反射
//我们看看该配件的自描述
//使用了GuidAttribute类,这里我使用了Reflector查看了该类的实现
//该类是这样描述的
//[AttributeUsage(AttributeTargets.Delegate | (AttributeTargets.Interface | (AttributeTargets.Enum | (AttributeTargets.Struct | (AttributeTargets.Class | AttributeTargets.Assembly)))), Inherited=false)]
//ArrtibuteTargets是枚举类型
//指出该类绑定了代理,接口,枚举,结构,类以及配件,并且不可继承
//这里的继承含义不是类的派生,而是指出属性不可继承,这意味假设存在派生类并对其类重新进行了属性绑定,那么派生类所绑定的属性将覆盖基类所绑定的属性
//不过,该类同时也是一个不可继承的类,这在类的定义中可以看到含有一个sealed修饰符
public interface IEnumerable
{
[System.Runtime.InteropService.DispIdAttribute(252)]
System.Collections.IEumerator GetEnumerator();
}
//为接口绑定属性
[System.Runtime.InteropServices.GuidAttribute("496B0ABF-CDEE-11d3-88E8-00902754C43A")]
public interface IEumerator
{
//返回当前集合中的元素
object Current
{
get;
}
//返回值true表示迭代器成功前进到集合中的下一个元素
//返回值false表示已经位于集合的末尾
bool MoveNext();
//恢复初始化指向的位置
void Reset();
}
}
客户端调用IEnumerable.GetEnumerator()方法实际上是通过IEnumerator的Current属性,该属性在派生类EnumeratorConcret实现,并实例化为EnumeratorConcret。当MoveNext()和Reset()方法被调用的时候,枚举器将保持当前的索引,这样客户端就可以通过Current属性访问集合中的元素。它的UML视图看起来像这样:
这有一个使用CSharp语法实现迭代的例子。Person类是一个可枚举的类。PersonsEnumerator类是一个枚举器类。注意如果类实现了以上两种接口那么Person类就具有了可枚举和枚举器两种身份。无论怎样,我们将看到一种更优雅的实现类的方法(这里我们遵循了一条重要的面向对象原则:创建一个可复用的类【对象】)。
using System.Collections;
public class Persons : IEnumerable
{
private class PersonsEnumerator : IEnumerator
{
private int index = -1;
private Persons P;
public PersonsEnumerator(Persons P)
{
this.P = P;
}
public bool MoveNext()
{
index++;
return P.m_Names.Length;
}
public void Reset()
{
index = -1;
}
public object Current
{
get
{
return P.m_Names[index];
}
}
}
string[] m_Names;
public Persons(params string[] Names)
{
m_Names = new string[Names.Length];
Names.CopyTo(m_Names,0);
}
private string this[int index]
{
get
{
return m_Names[index];
}
set
{
m_Names[index] = value;
}
}
}
class Program
{
static void Main(string[] args)
{
Persons arrPersons = new Persons(“Michel”,”Christine”,”Mathieu”,”Julien”);
foreach (string s in arrPersons)
{
Console.WriteLine(s);
Console.ReadLine();
}
}
}
该程序将输出:
Christine
Mathieu
Julien
这段代码中的大部分语句用来实现枚举器的索引行为,我们更希望看到 CSharp2.0 达到同样功能却戏剧性的缩小的代码数量。
{
static void Main(string[] args)
{
Persons arrPersons = new Persons(“Michel”,”Christine”,”Mathieu”,”Julien”);
IEnumerator e = arrPersons.GetEnumerator();
While (e.MoveNext())
{
Console.WriteLine((string)e.Current);
Console.ReadLine();
}
}
}
单一枚举包含多个枚举构造器
一个可枚举类不一定要服从实现 IEnumerable 接口。这个责任可以被委托到其他类中实现。例如:
using System.Collections;
public class Persons // 这里没有实现IEnumerable接口
{
private class PersonsEnumerator :IEnumerator
{
…
}
private class PersonsEnumerable :IEnumerable
{
private Persons m_Persons;
inernal PersonEnumerable(Persons persons)
{
m_Persons = persons;
}
IEnumerator IEnumerable.GetEnumerator()
{
return new PersonsEnumerator(m_Persons);
}
}
public IEnumerable InOrder
{
get
{
return new PersonsEnumerable(this);
}
}
}
class Program
{
static void Main(string[] args)
{
Persons arrPersons = new Persons(“Michel”,”Christine”,”Mathieu”,”Julien”);
foreach (string s in arrPersons.InOrder)
{
Console.WriteLine(s);
Console.ReadLine();
}
}
}
CSharp 1.0 迭代器的缺点
无疑CSharp1.0迭代器语法对于它所提供的功能来说显得太笨拙了。处出于这个原因,大多数开发者不愿意使用它。此外对这种语法而言,尝试枚举一些特殊的集合(象这种二叉树)那么很快将成为一场恶梦。
C#2.0通过强大的yield return关键字无缝实现了迭代器模式。特别是减轻了开发者实现一个枚举器和可枚举类的负担。这里我重写了一个前面的例子:
using System.Collections;
public class Persons : IEnumerable
{
string[] m_Names;
public Persons(params string[] Names)
{
m_Names = new string[Names.Length];
Names.CopyTo(m_Names,0);
}
public IEnumerator GetEnumerator()
{
foreach (string s in m_Names)
{
yield return s;
}
}
}
class Program
{
static void Main(string[] args)
{
Persons arrPersons = new Persons(“Michel”,”Christine”,”Mathieu”,”Julien”);
foreach (string s in arrPersons)
{
Console.WriteLine(s);
Console.ReadLine();
}
}
}
注意:方法能对yield return关键字多次调用.实例如下:
using System.Collections;
public class Persons : IEnumerable
{
string[] m_Names;
public Persons(params string[] Names)
{
m_Names = new string[Names.Length];
Names.CopyTo(m_Names,0);
}
public IEnumerator GetEnumerator()
{
yield return "Michel";
yield return "Chirstine";
yield return "Mathieu";
yield return "Julien";
}
}
class Program
{
static void Main(string[] args)
{
Persons arrPersons = new Persons("Michel","Christine","Mathieu","Julien");
foreach (string s in arrPersons)
{
Console.WriteLine(s);
Console.ReadLine();
}
}
}
Michel
Christine
Mathieu
Julien
在.NET Framework 2.0中IEnumerable和IEnumerator接口都提供了一种范型形态。
{
[System.Runtime.InteropServices.ComVisibleAttribute(false)]
[Systen,CLSCompliantAttribute(false)]
public interface IEnumerable<T>
{
System.Collections.Generic.IEnumerator<T> GetEnumerator();
}
[System.Runtime.InteropServices.ComVisibleAttribute(false)]
[Systen,CLSCompliantAttribute(false)]
public interface IEnumerator<T> : System.Idisposable
{
T Current
{
get;
}
bool MoveNext();
}
}
我们注意到IEnumerator接口实现了IDisposable接口,同时还注意到IEnumerator.Reset()方法已经被去掉了。现在我们能告诉编译器它枚举的是一个字符串集合而不是一个对象集合。
using Systen,Collections.Generic;
public class Persons : IEnumerable < string >
{
string[] m_Names;
public Persons(params string[] Names)
{
m_Names = new string[Names.Length];
Names.CopyTo(m_Names,0);
}
public IEnumerator<string> GetEnumerator()
{
foreach (string s in m_Names)
yield return s;
}
}
…
单一枚举包含多个枚举构造器
using Systen,Collections.Generic;
public class Persons : IEnumerable < string >
{
string[] m_Names;
public Persons(params string[] Names)
{
m_Names = new string[Names.Length];
Names.CopyTo(m_Names,0);
}
public IEnumerable<string> Reverse
{
get
{
for (int i = m_Names.Length - 1; i >= 0; i--)
yield return m_Names[i];
}
}
public IEnumerable<string> PositionsPaires
{
get
{
for (int i= 0; i<= m_Names.Length; i++,i++)
yield return m_Names[i];
}
}
public IEnumerable<string> Concat
{
get
{
foreach (string s in Reverse)
yield return s;
foreach (string s in PositionsPaires)
yield return s;
}
}
}
class Program
{
static void Main (string[] args)
{
Persons arrPersons = new Persons("Michel","Christine","Mathieu","Julien");
Console.WriteLine("-->Iterator Reverse");
foreach (string s in arrPersons.Reverse) Console.WriteLine(s);
Console.WriteLine("-->Iterator PositionPaires");
foreach (string s in arrPersons. PositionPaires) Console.WriteLine(s);
Console.WriteLine("-->Iterator Concat");
foreach (string s in arrPersons.Concat) Console.WriteLine(s);
Console.ReadLine();
}
}
程序将产生以下输出:
-->Iterator Reverse
Julien
Mathieu
Christine
Michel
-->Iterator PositionsPaires
Michel
Mathieu
-->Iterator Concat
Julien
Mathieu
Christine
Michel
Michel
Mathieu
你如果希望枚举集合中的一些子元素,在这个例子中,yield break关键字正确的告知客户它将中止循环。
public IEnumerator < string > GetEnumerator()
{
for (int I = 0;I < 2; I++)
yield return m_Names[i];
yield break;
Console.WriteLine(“Hello”); //警告:检测到一个不能到达的代码段
}
…
Christine
结果是,书写在yield break指令后的代码不会被编译,编译器如果发现这些不能到达的语句将发出一个警告信息。
yield return和yield break关键字不能用在含有ref和out参数的方法中,可以预示的是包含ref和out这些关键字的方法并不能返回任何yield关键字约束的内容信息。
using System.Collections.Generic;
public class Node < T >
{
public Node ( T item, Node<T> leftNode, Node<T> rightNode)
{
m_Item = item;
m_LeftNode = leftNode;
m_RightNode = rightNode;
}
public Node<T> m_LeftNode;
public Node<T> m_RightNode;
public T m_Item;
}
public class BinaryTree < T >
{
Node<T> m_Root;
public BinaryTree(Node<T> Root)
{
m_Root = Root;
}
public IEnumerable<T> InOrder
{
get
{
return PrivateScanInOrder(m_Root);
}
}
private IEnumerable<T> PrivateScanInOrder(Node<T> root)
{
if (root.m_LeftNode != null)
foreach (T item in PrivateScanInOrder(root.m_LeftNode))
{
yield return item;
}
yield return root.m_Item;
if (root.m_RightNode != null)
foreach (T item in PrivateScanInOrder(root.m_RightNode))
{
yield return item;
}
}
}
class Program
{
static void Main(string[] args)
{
BinaryTree<string> binaryTree = new BinaryTree<string>(new Node<string>("A",new Node<string>("B",null,null),new Node<string>("C",new Node<string>("D",null,null),new Node<string>(“D”,null,null))));
foreach (string s in binaryTree.InOrder)
{
Console.WriteLine(s);
Console.ReadLine();
}
}
}
在Main方法中二叉树如下表示:
A
D
C
E
using System.Collections.Generic;
class AClass
{
public Ienumerable<string> AnIterateur()
{
yield return “str1”;
yield return “str2”;
yield return “str3”;
}
}
class Program
{
static void Main(string[] args)
{
AClass collec = new AClass();
foreach ( string s in collec.AnIterateur())
{
Console.WriteLine(s);
Console.ReadLine();
}
}
}
接着我们使用了Reflector查看编译后的配件:
为了看的更清楚,我们反编译了Main()方法。接着我们会看到一个名为<AnIteratuer>d_0类实例被创建。在前面的论述中我们提过一个匿名方法是某种封闭(Closure)的实例。而C#2.0的迭代器就是一种特殊的封闭类型。