• [C#基础知识系列]专题十二:迭代器


    引言:

       在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 
    109         public void Reset()
    110         {
    111             index = 0;
    112         }
    113 
    114         #endregion 
    115     }
    116 }

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

    三、使用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()方法,所以迭代器可以延迟计算,下面通过一个示例来演示迭代器的延迟计算:

    namespace 迭代器延迟计算Demo
    {
        class Program
        {
            /// <summary>
            ///  演示迭代器延迟计算
            /// </summary>
            /// <param name="args"></param>
            static void Main(string[] args)
            {
                // 测试一
                //WithIterator();
                //Console.Read();
    
                // 测试二
                //WithNoIterator();
                //Console.Read();
    
                // 测试三
                foreach (int j in WithIterator())
                {
                    Console.WriteLine("在main输出语句中,当前i的值为:{0}", j);
                }
    
                Console.Read();
            }
    
            public static IEnumerable<int> WithIterator()
            {
                for (int i = 0; i < 5; i++)
                {
                    Console.WriteLine("在WithIterator方法中的, 当前i的值为:{0}", i);
                    if (i > 1)
                    {
                        yield return i;
                    }
                }
            }
    
            public static IEnumerable<int> WithNoIterator()
           {
               List<int> list = new List<int>();
               for (int i = 0; i < 5; i++)
               {
                   Console.WriteLine("当前i的值为:{0}", i);
                   if (i > 1)
                   {
                       list.Add(i);
                   }
               }
    
               return list;
           }
        }
    }

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

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

    public static IEnumerable<int> WithIterator()
    {
           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 中的特性。

     

    附件:源程序代码https://files.cnblogs.com/zhili/%E8%BF%AD%E4%BB%A3%E5%99%A8Demo.zip

     破解版的Reflector工具:https://files.cnblogs.com/zhili/Reflector.zip

  • 相关阅读:
    下载文件c#
    系统蓝屏重起:如何修改设置,记录系统蓝屏重起的错误
    Repeater中的行数
    2010617 重装系统遇到的问题
    ajax 修改select的值 返回的值中有逗号
    GPS数据接收 串口调试感受
    Nios II实用之音频控制
    【笔记】VB控件MSComm功能介绍
    【摘】程序员版同桌的你
    AJAX Control Toolkit ——DragPanelExtender(拖拽面板)
  • 原文地址:https://www.cnblogs.com/zhili/p/Interator.html
Copyright © 2020-2023  润新知