Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1039477
  • 博文数量: 243
  • 博客积分: 3053
  • 博客等级: 中校
  • 技术积分: 2975
  • 用 户 组: 普通用户
  • 注册时间: 2009-05-02 21:11
文章分类

全部博文(243)

文章存档

2013年(2)

2012年(20)

2011年(5)

2010年(114)

2009年(102)

我的朋友

分类:

2010-07-15 14:30:38

在foreach语句中使用枚举,可以迭代集合中的元素,且无需知道集合中的元素个数。图5-7显示了调用foreach方法的客户机和集合之间的关系。数组或集合执行带GetEumerator()方法的IEumerable接口。GetEumerator()方法返回一个执行IEumerable接口的枚举。接着,foreach语句就可以使用IEumerable接口迭代集合了。

图  5-7

提示:

GetEnumerator()方法用IEnumerable接口定义。foreach语句并不真的需要在集合类中执行这个接口。有一个名为GetEnumerator()的方法,返回实现了IEnumerator接口的对象就足够了。

5.6.1  IEnumerator接口

foreach语句使用IEnumerator接口的方法和属性,迭代集合中的所有元素。这个接口中的属性和方法如表5-4所示。

表  5-4

IEnumerator接口的方法和属性

说    明

MoveNext()

MoveNext()方法移动到集合的下一个元素上,如果有这个元素,该方法就返回true。如果集合不再有更多的元素,该方法就返回false

Current

属性Current返回光标所在的元素

Reset()

Reset()方法将光标重新定位于集合的开头。许多枚举会抛出NotSupportedException异常

5.6.2  foreach语句

C#的foreach语句不会解析为IL代码中的foreach语句。C#编译器会把foreach语句转换为IEnumerable接口的方法和属性。下面是一个简单的foreach语句,它迭代persons数组中的所有元素,并逐个显示它们:

foreach (Person p in persons)

{

  Console.WriteLine(p);

}

foreach语句会解析为下面的代码段。首先,调用GetEnumerator()方法,获得数组的一个枚举。在while循环中—— 只要MoveNext()返回true—— 用Current属性访问数组中的元素:

IEnumerator enumerator = persons. GetEnumerator();

while (enumerator.MoveNext())

{

   Person p = (Person) enumerator.Current;

   Console.WriteLine(p);

}

5.6.3  yield语句

C# 1.0使用foreach语句可以轻松地迭代集合。在C# 1.0中,创建枚举器仍需要做大量的工作。C# 2.0添加了yield语句,以便于创建枚举器。

yield return语句返回集合的一个元素,并移动到下一个元素上。yield break可停止迭代。

下面的例子是用yield return语句实现一个简单集合的代码。类HelloCollection包含GetEnumerator()方法。该方法的实现代码包含两个yield return语句,它们分别返回字符串Hello和World。

using System;

using System.Collection;

namespace Wrox.ProCAharp.Arrays

{

  public class HelloCollection

  {

    public IEumerator GetEumerator()

    {

    yield return "Hello";

    yield return "World";

}

}

}

警告:

包含yield语句的方法或属性也称为迭代块。迭代块必须声明为返回IEnumerator或IEnumerable接口。这个块可以包含多个yield return语句或yield break语句,但不能包含return语句。

现在可以用foreach语句迭代集合了:

public class Program

{

   HelloCollection helloCollection = new HelloCollection();

   foreach (string s in helloCollection)

{

      Console.WriteLine(s);

}

}

}

使用迭代块,编译器会生成一个yield 类型,其中包含一个状态机,如下面的代码所示。yield 类型执行IEnumerator和IDisposable接口的属性和方法。在下面的例子中,可以把yield 类型看作内部类Enumerator。外部类的GetEnumerator()方法实例化并返回一个新的yield 类型。在yield 类型中,变量state定义了迭代的当前位置,每次调用MoveNext()时,当前位置都会改变。MoveNext()封装了迭代块的代码,设置了current变量的值,使Current属性根据位置返回一个对象。

public class HelloCollection

{

public IEnumerator GetEnumerator()

  {

    Enumerator enumerator = new Enumerator();

    return enumerator;

}

public class Enumerator : IEnumerator, IDisposable

{

  private int state;

  private object current;

  public Enumerator(int state)

  {

    this.state = state;

}

bool System.Collections.IEnumerator.MoveNext()

{

  switch (state)

  {

     case 0:

       current = "Hello";

       state = 1;

               return true;

              case 1:

                current = "World";

                state = 2;

                return true;

              case 2:

                break;

           }

           return false;

        }

        void System.Collections.IEnumerator.Reset()

        {

          throw new NotSupportedException();

}

object System.Collections.IEnumerator.Current

{

          get

          {

          return current;

}

}

        void IDisposable.Dispose()

        {

}

}

}

现在使用yield return语句,很容易实现允许以不同方式迭代集合的类。类MusicTitles可以用默认方式通过GetEnumerator()方法迭代标题,用Reverse()方法逆序迭代标题,用Subset()方法搜索子集:

public class MusicTitles

{

  string[] names = {

         "Tubular Bells", "Hergest Ridge",

         "Ommadawn", "Platinum");

  public IEnumerator GetEnumerator()

  {

     for (int i = 0; i < 4; i++)

     {

     yield return names[i];

}

}

  public IEnumerable Reverse()

  {

     for (int i = 3; i >= 0; i–)

     {

             yield return names[i];

}

}

public IEnumerable Subset( int index, int length)

{

        for (int i = index; i < index + length; i++)

        {

            yield return names[i];

}

}

}

迭代字符串数组的客户代码先使用GetEnumerator()方法,该方法不必在代码中编写,因为这是默认使用的方法。然后逆序迭代标题,最后将索引和要迭代的元素个数传送给Subset()方法,来迭代子集:

MusicTitles titles = new MusicTitles();

foreach(string title in titles)

{

  ConsoleWriteLine(title);

}

ConsoleWriteLine();

ConsoleWriteLine("reverse");

foreach(string title in titles.Reverse())

{

  ConsoleWriteLine(title);

}

ConsoleWriteLine();

ConsoleWriteLine("subset");

foreach(string title in titles.Subset(2, 2))

{

  ConsoleWriteLine(title);

}

使用yield语句还可以完成更复杂的任务,例如从yield return中返回枚举器。

在TicTacToe游戏中有9个域,玩家轮流在这些域中放置“十”字或一个圆。这些移动操作由GameMoves类模拟。方法Cross()和Circle()是创建迭代类型的迭代块。变量cross和circle在GameMoves类的构造函数中设置为Cross()和Circle()方法。这些域不设置为调用的方法,而是设置为用迭代块定义的迭代类型。在Cross()迭代块中,将移动操作的信息写到控制台上,并递增移动次数。如果移动次数大于9,就用yield break停止迭代;否则,就在每次迭代中返回yield类型cross的枚举对象。Circle()迭代块非常类似于Cross()迭代块,只是它在每次迭代中返回circle迭代类型。

public calss GameMoves

{

  private IEnumerator cross;

  private IEnumerator circle;

  public GameMoves()

  {

    cross = Cross();

    circle = Circle();

}

  private int move = 0;

  public IEnumerator Cross()

  {

     while (true)

     {

       Console.WriteLine("Cross, move {0}", move);

       move++;

       if (move > 9)

         yield break;

       yield return circle;

     }

}

  public IEnumerator Circle()

  {

     while (true)

     {

       Console.WriteLine("Circle, move {0}", move);

       move++;

       if (move > 9)

         yield break;

       yield return cross;

     }

}

在客户程序中,可以以如下方式使用GameMoves类。将枚举器设置为由game.Cross()返回的枚举器类型,以设置第一次移动。enumerator.MoveNext调用以迭代块定义的一次迭代,返回另一个枚举器。返回的值可以用Current属性访问,并设置为enumerator变量,用于下一次循环:

GameMoves game = new GameMoves();

IEnumerator enumerator = game.Cross();

while (enumerator.MoveNext())

{

  enumerator = (IEnumerator) enumerator.Current;

}

这个程序的输出会显示交替移动的情况,直到最后一次移动:

Cross, move 0

Circle, move 1

Cross, move 2

Circle, move 3

Cross, move 4

Circle, move 5

Cross, move 6

Circle, move 7

Cross, move 8

阅读(5982) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~