引言:

   在C# 1.0中我们经常使用foreach来遍历一个集合中的元素,然而一个类型要能够使用foreach关键字来对其进行遍历必须实现IEnumerableIEnumerable<T>接口,(之所以来必须要实现IEnumerable这个接口,是因为foreach是迭代语句,要使用foreach必须要有一个迭代器才行的,然而IEnumerable接口中就有IEnumerator GetEnumerator()方法是返回迭代器的,所以实现了IEnumerable接口,就必须实现GetEnumerator()这个方法来返回迭代器,有了迭代器就自然就可以使用foreach语句了),然而在C# 1.0中要获得迭代器就必须实现IEnumerable接口中的GetEnumerator()方法,然而要实现一个迭代器就必须实现IEnumerator接口中的bool MoveNext()和void Reset()方法,然而 C# 2.0中提供 yield关键字来简化迭代器的实现,这样在C# 2.0中如果我们要自定义一个迭代器就容易多了。下面就具体介绍了C# 2.0 中如何提供对迭代器的支持.

一、迭代器的介绍

   迭代器大家可以想象成数据库的游标,即一个集合中的某个位置,C# 1.0中使用foreach语句实现了访问迭代器的内置支持,使用foreach使我们遍历集合更加容易(比使用for语句更加方便,并且也更加容易理解),foreach被编译后会调用GetEnumerator来返回一个迭代器,也就是一个集合中的初始位置(foreach其实也相当于是一个语法糖,把复杂的生成代码工作交给编译器去执行)。

二、C#1.0如何实现迭代器

   在C# 1.0 中实现一个迭代器必须实现IEnumerator接口,下面代码演示了传统方式来实现一个自定义的迭代器:

 
  
  1. using System;  
  2. using System.Collections;  
  3.    
  4.  namespace 迭代器Demo  
  5.  {  
  6.      class Program  
  7.      {  
  8.          static void Main(string[] args)  
  9.          {  
  10.              Friends friendcollection = new Friends();  
  11.              foreach (Friend f in friendcollection)  
  12.              {  
  13.                  Console.WriteLine(f.Name);  
  14.              }  
  15.    
  16.              Console.Read();  
  17.          }  
  18.      }  
  19.    
  20.      /// <summary>  
  21.      ///  朋友类  
  22.      /// </summary>  
  23.      public class Friend  
  24.      {  
  25.          private string name;  
  26.          public string Name  
  27.          {  
  28.              get { return name; }  
  29.              set { name = value; }  
  30.          }  
  31.          public Friend(string name)  
  32.          {  
  33.              this.name = name;  
  34.          }  
  35.      }  
  36.    
  37.      /// <summary>  
  38.      ///   朋友集合  
  39.      /// </summary>  
  40.      public class Friends : IEnumerable  
  41.      {  
  42.          private Friend[] friendarray;  
  43.    
  44.          public Friends()  
  45.          {  
  46.              friendarray = new Friend[]  
  47.              {  
  48.                  new Friend("张三"),  
  49.                  new Friend("李四"),  
  50.                  new Friend("王五")  
  51.              };  
  52.          }  
  53.    
  54.          // 索引器  
  55.          public Friend this[int index]  
  56.          {  
  57.              get { return friendarray[index]; }  
  58.          }  
  59.    
  60.          public int Count  
  61.          {  
  62.              get { return friendarray.Length; }  
  63.          }  
  64.    
  65.          // 实现IEnumerable<T>接口方法  
  66.         public  IEnumerator GetEnumerator()  
  67.          {  
  68.              return new FriendIterator(this);  
  69.          }  
  70.      }  
  71.    
  72.      /// <summary>  
  73.      ///  自定义迭代器,必须实现 IEnumerator接口  
  74.      /// </summary>  
  75.      public class FriendIterator : IEnumerator  
  76.      {  
  77.          private readonly Friends friends;  
  78.          private int index;  
  79.          private Friend current;  
  80.          internal FriendIterator(Friends friendcollection)  
  81.          {  
  82.              this.friends = friendcollection;  
  83.              index = 0;  
  84.          }  
  85.    
  86.          #region 实现IEnumerator接口中的方法  
  87.          public object Current  
  88.          {  
  89.              get 
  90.              {  
  91.                  return this.current;  
  92.              }  
  93.          }  
  94.    
  95.          public bool MoveNext()  
  96.          {  
  97.              if (index + 1 > friends.Count)  
  98.              {  
  99.                  return false;  
  100.              }  
  101.              else 
  102.              {  
  103.                  this.current = friends[index];  
  104.                  index++;  
  105.                  return true;  
  106.              }  
  107.          }  
  108.          public void Reset()  
  109.          {  
  110.              index = 0;  
  111.          }  
  112.          #endregion   
  113.      }  
  114.  } 

运行结果(上面代码中都有详细的注释,这里就不说明了,直接上结果截图):

三、使用C#2.0的新特性简化迭代器的实现

   在C# 1.0 中要实现一个迭代器必须实现IEnumerator接口,这样就必须实现IEnumerator接口中的MoveNext、Reset方法和Current属性,从上面代码中看出,为了实现FriendIterator迭代器需要写40行代码,然而在C# 2.0 中通过yield return语句简化了迭代器的实现,下面看看C# 2.0中简化迭代器的代码:

 
  
  1. namespace 简化迭代器的实现  
  2.  {  
  3.      class Program  
  4.      {  
  5.          static void Main(string[] args)  
  6.          {  
  7.              Friends friendcollection = new Friends();  
  8.              foreach (Friend f in friendcollection)  
  9.              {  
  10.                  Console.WriteLine(f.Name);  
  11.              }  
  12.    
  13.              Console.Read();  
  14.          }  
  15.      }  
  16.    
  17.      /// <summary>  
  18.      ///  朋友类  
  19.      /// </summary>  
  20.      public class Friend  
  21.      {  
  22.          private string name;  
  23.          public string Name  
  24.          {  
  25.              get { return name; }  
  26.              set { name = value; }  
  27.          }  
  28.          public Friend(string name)  
  29.          {  
  30.              this.name = name;  
  31.          }  
  32.      }  
  33.    
  34.      /// <summary>  
  35.      ///   朋友集合  
  36.      /// </summary>  
  37.      public class Friends : IEnumerable  
  38.      {  
  39.          private Friend[] friendarray;  
  40.    
  41.          public Friends()  
  42.          {  
  43.              friendarray = new Friend[]  
  44.              {  
  45.                  new Friend("张三"),  
  46.                  new Friend("李四"),  
  47.                  new Friend("王五")  
  48.              };  
  49.          }  
  50.    
  51.          // 索引器  
  52.          public Friend this[int index]  
  53.          {  
  54.              get { return friendarray[index]; }  
  55.          }  
  56.    
  57.          public int Count  
  58.          {  
  59.              get { return friendarray.Length; }  
  60.          }  
  61.    
  62.          // C# 2.0中简化迭代器的实现  
  63.          public IEnumerator GetEnumerator()  
  64.          {  
  65.              for (int index = 0; index < friendarray.Length; index++)  
  66.              {  
  67.                  // 这样就不需要额外定义一个FriendIterator迭代器来实现IEnumerator  
  68.                  // 在C# 2.0中只需要使用下面语句就可以实现一个迭代器  
  69.                  yield return friendarray[index];  
  70.              }  
  71.          }  
  72.      }  
  73.  } 

  在上面代码中有一个yield return 语句,这个语句的作用就是告诉编译器GetEnumerator方法不是一个普通的方法,而是实现一个迭代器的方法,当编译器看到yield return语句时,编译器知道需要实现一个迭代器,所以编译器生成中间代码时为我们生成了一个IEnumerator接口的对象,大家可以通过Reflector工具进行查看,下面是通过Reflector工具得到一张截图:

  从上面截图可以看出,yield return 语句其实是C#中提供的另一个语法糖,简化我们实现迭代器的源代码,把具体实现复杂迭代器的过程交给编译器帮我们去完成,看来C#编译器真是做得非常人性化,把复杂的工作留给自己做,让我们做一个简单的工作就好了。

四、迭代器的执行过程

 为了让大家更好的理解迭代器,下面列出迭代器的执行流程:

五、迭代器的延迟计算

  从第四部分中迭代器的执行过程中可以知道迭代器是延迟计算的, 因为迭代的主体在MoveNext()中实现(因为在MoveNext()方法中访问了集合中的当前位置的元素),Foreach中每次遍历执行到in的时候才会调用MoveNext()方法,所以迭代器可以延迟计算,下面通过一个示例来演示迭代器的延迟计算:

 
  
  1. namespace 迭代器延迟计算Demo  
  2. {  
  3.     class Program  
  4.     {  
  5.         /// <summary>  
  6.         ///  演示迭代器延迟计算  
  7.         /// </summary>  
  8.         /// <param name="args"></param>  
  9.         static void Main(string[] args)  
  10.         {  
  11.             // 测试一  
  12.             //WithIterator();  
  13.             //Console.Read();  
  14.  
  15.             // 测试二  
  16.             //WithNoIterator();  
  17.             //Console.Read();  
  18.  
  19.             // 测试三  
  20.             foreach (int j in WithIterator())  
  21.             {  
  22.                 Console.WriteLine("在main输出语句中,当前i的值为:{0}", j);  
  23.             }  
  24.  
  25.             Console.Read();  
  26.         }  
  27.  
  28.         public static IEnumerable<int> WithIterator()  
  29.         {  
  30.             for (int i = 0; i < 5; i++)  
  31.             {  
  32.                 Console.WriteLine("在WithIterator方法中的, 当前i的值为:{0}", i);  
  33.                 if (i > 1)  
  34.                 {  
  35.                     yield return i;  
  36.                 }  
  37.             }  
  38.         }  
  39.  
  40.         public static IEnumerable<int> WithNoIterator()  
  41.        {  
  42.            List<int> list = new List<int>();  
  43.            for (int i = 0; i < 5; i++)  
  44.            {  
  45.                Console.WriteLine("当前i的值为:{0}", i);  
  46.                if (i > 1)  
  47.                {  
  48.                    list.Add(i);  
  49.                }  
  50.            }  
  51.  
  52.            return list;  
  53.        }  
  54.     }  

当运行测试一的代码时,控制台中什么都不输出,原因是生成的迭代器延迟了值的输出,大家可以用Reflector工具反编译出编译器生成的中间语言代码就可以发现原因了,下面是一张截图:

从图中可以看出,WithIterator()被编译成下面的代码了(此时编译器把我们自己方法体写的代码给改了):

 
  
  1. public static IEnumerable<int> WithIterator()  
  2. {  
  3.        return new <WithIterator>d_0(-2);    

  从而当我们测试一的代码中调用WithIterator()时,对于编译器而言,就是实例化了一个<WithIterator>d_0的对象(<WithIterator>d_0类是编译看到WithIterator方法中包含Yield return 语句生成的一个迭代器类),所以运行测试一的代码时,控制台中什么都不输出。

当运行测试二的代码时,运行结果就如我们期望的那样输出(这里的运行结果就不解释了,列出来是为了更好说明迭代器的延迟计算):

当我们运行测试三的代码时,运行结果就有点让我们感到疑惑了, 下面先给出运行结果截图,然后在分析原因。

可能刚开始看到上面的结果很多人会有疑问,为什么2,3,4会运行两次的呢?下面具体为大家分析下为什么会有这样的结果。

测试代码三中通过foreach语句来遍历集合时,当运行in的时候就会运行IEnumerator.MoveNext()方法,下面是上面代码的MoveNext()方法的代码截图:

  从截图中可以看到有Console.WriteLine()语句,所以用foreach遍历的时候才会有结果输出(主要是因为foreach中in 语句调用了MoveNext()方法),至于为什么2,3,4会运行两行,主要是因为这里有两个输出语句,一个是WithIterator方法体内for语句中的输出语句,令一个是Main函数中对WithIterator方法返回的集合进行迭代的输出语句,在代码中都有明确指出,相信大家经过这样的解释后就不难理解测试三的运行结果了。

六、小结

  本专题主要介绍了C# 2.0中通过yield return语句对迭代器实现的简化,然而对于编译器而言,却没有简化,它同样生成了一个类去实现IEnumerator接口,只是我们开发人员去实现一个迭代器得到了简化而已。希望通过本专题,大家可以对迭代器有一个进一步的认识,并且迭代器的延迟计算也是Linq的基础,本专题之后将会和大家介绍C# 3.0中提出的新特性,然而C# 3.0中提出来的Lambda,Linq可以说是彻底改变我们编码的风格,后面的专题中将会和大家一一分享我所理解C# 3.0 中的特性。