.Net学习难点讨论系列11 - foreach与迭代器,枚举数与可枚举值

本文主要整理于ASP.NET2.0开发指南部分内容、C#图解教程

要使一个自定义的集合类可以与foreach一起工作,我们需要实现IEnumerable接口(GetEnumerator方法)或实现IEnumerator<T>接口。

在.NET Framework枚举迭代相关类设计中IEnumerator是基础,其包含三个成员其中一个Current属性,两个函数Movenext与Reset。这些是一个集合对象可枚举的基础。而IEnumerable接口只包含一个GetEnumerator方法,作用就是返回一个IEnumerator对象。

.NET2.0新增的泛型接口IEnumerator<T>实现了IEnumerator与IDisposal接口,该接口自身包含一个Current属性,该属性返回一个T类型的对象。我们实现此接口时,需要实现其自身Current属性及父接口IEnumerator与IDisposal的方法。这其中特别需要注意的是,我们需要显式实现IEnumerator中的Current属性:

1 object System.Collections.IEnumerator.Current
2 {
3     get { throw new NotImplementedException(); }
4 }

另一个泛型接口IEnumerable<T>继承自IEnumerable接口,其自身包含一个GetEnumerator方法返回泛型版本的IEnumerator<T>对象。类似于实现IEnumerator<T>,在实现IEnumerable<T>时,除了实现返回IEnumerator<T>的GetEnumerator方法,还要显示实现IEnumerable中的GetEnumerator方法:

1 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
2 {
3     throw new NotImplementedException();
4 }

了解了这些后就很容易解释迭代器(yield)了,迭代器提供给我们最简单的创建一个IEnumerator<T>对象或IEnumerable<T>的途径,见下面的代码段:

 1 //返回泛型枚举数
 2 public IEnumerator<string> SampleMethod1()
 3 {
 4     yield return "tree";
 5     yield return "flower";
 6 }
 7 
 8 //返回泛型可枚举对象
 9 public IEnumerable<string> SampleMethod2()
10 {
11     yield return "tree";
12     yield return "flower";
13 }

 

综合以上,下面看一个完整的例子:

 

 1 public class Persons : IEnumerable
 2 {
 3     public string[] m_Names;
 4     public Persons(params string[] Names)
 5     {
 6         m_Names = new string[Names.Length];
 7         Names.CopyTo(m_Names, 0);
 8     }
 9 
10     //实现索引器
11     public string this[int index]
12     {
13         get
14         {
15             return m_Names[index];
16         }
17         set
18         {
19             m_Names[index] = value;
20         }
21     }
22 
23     //实现IEnumerable成员
24     //需返回一个IEnumerator类型的对象
25     #region IEnumerable 成员
26 
27     IEnumerator IEnumerable.GetEnumerator()
28     {
29         return new PersonsEnumerator(this);
30     }
31 
32     #endregion
33 }
34 
35 public class PersonsEnumerator : IEnumerator
36 {
37     int index = -1;
38     Persons P;
39     public PersonsEnumerator(Persons P)
40     {
41         this.P = P;
42     }
43     
44     #region IEnumerator 成员
45 
46     object IEnumerator.Current
47     {
48         get
49         {
50             return P.m_Names[index];
51         }
52     }
53 
54     bool IEnumerator.MoveNext()
55     {
56         int tempIndex = ++index;
57         if (tempIndex >= P.m_Names.Length)
58         {
59             return false;
60         }
61         else
62         {
63             return true;
64         }
65     }
66 
67     void IEnumerator.Reset()
68     {
69         index = -1;
70     }
71     #endregion
72 }

使用(非泛型)迭代器的方式:

1 static void Main()
2 {
3     Persons arrPersons = new Persons("Michel", "Rebecca", "Polaris");
4     foreach (string s in arrPersons)
5     {
6         Console.WriteLine(s);
7     }
8 }

 

在C# 2.0中我们可以通过新增的关键字 – yeild,来方便的实现一个迭代器。而且.NET2.0中新增的泛型可以使迭代器强类型化。

一步步进行:首先我们将上述程序改写为用yield关键字实现(仍然是非泛型)。

 1 public class Persons : IEnumerable
 2 {
 3     public string[] m_Names;
 4     public Persons(params string[] Names)
 5     {
 6         m_Names = new string[Names.Length];
 7         Names.CopyTo(m_Names, 0);
 8     }
 9 
10     #region IEnumerable 成员
11 
12     IEnumerator IEnumerable.GetEnumerator()
13     {
14         for (int i = 0; i < m_Names.Length; i++)
15         {
16             yield return m_Names[i];
17         }
18     }
19 
20     #endregion
21 }

接下来,我们将此方法改写为泛型的实现:

 1 public class Persons<T> : IEnumerable<T>
 2 {
 3     public T[] m_Names;
 4     public Persons(params T[] Names)
 5     {
 6         m_Names = new T[Names.Length];
 7         Names.CopyTo(m_Names, 0);
 8     }
 9 
10     #region IEnumerable<T> 成员
11 
12     IEnumerator<T> IEnumerable<T>.GetEnumerator()
13     {
14         for (int i = 0; i < m_Names.Length; i++)
15         {
16             yield return m_Names[i];
17         }
18     }
19 
20     #endregion
21 }

泛型版迭代器的使用:

1 static void Main()
2 {
3     Persons<string> arrPersons = new Persons<string>("Michel", "Rebecca", "Polaris");
4     foreach (string s in arrPersons)
5     {
6         Console.WriteLine(s);
7     }
8 }

 

上文基本列出了怎样使用yield实现一个迭代器。

下面来说一下yield的一些使用方式:

1. 终止yield迭代的方式

 1 IEnumerator IEnumerable.GetEnumerator()
 2 {
 3     for (int i = 0; i < m_Names.Length; i++)
 4     {
 5         yield return m_Names[i];
 6         if (i >= 1)
 7         {
 8             yield break;
 9         }
10     }
11 }

2. 逆序迭代内容

1 IEnumerator IEnumerable.GetEnumerator()
2 {
3     for (int i = (m_Names.Length - 1); i >= 0; --i)
4     {
5         yield return m_Names[i];
6     }
7 }

3. 迭代器的另一种实现,返回实现IEnumerable<T>接口的对象,不实现GetEnumerator()方法。

 1 public class Persons<T>
 2 {
 3     private T[] m_Names;
 4     public Persons(params T[] Names)
 5     {
 6         m_Names = new T[Names.Length];
 7         Names.CopyTo(m_Names, 0);
 8     }
 9 
10     public IEnumerable<T> GetPersons()
11     {
12         for (int i = 0; i < m_Names.Length; i++)
13         {
14             yield return m_Names[i];
15         }
16     }
17 }
18 
19 class Program
20 {
21     static void Main()
22     {
23         Persons<string> arrPersons = new Persons<string>("Michel", "Rebecca", "Polaris");
24         foreach (string s in arrPersons.GetPersons())
25         {
26             Console.WriteLine(s);
27         }
28     }
29 }

 

posted @ 2009-10-15 13:47  hystar  阅读(955)  评论(2编辑  收藏  举报