zoukankan      html  css  js  c++  java
  • C# 迭代器、枚举器、IEnumerable和IEnumerator

    开始之前先思考几个问题:

    • 为什么集合可以使用foreach来遍历
    • 不用foreach能不能遍历各元素
    • 为什么在foreach中不能修改item的值?
    • 要实现foreach需要满足什么条件?
    • 为什么Linq to Object中要返回IEnumerable?

    一、枚举器和可枚举类型

    1、什么是可枚举类型?

    可枚举类是指实现了IEnumerable接口的类,比如数组就是可枚举类型;下面展示了一个可枚举类的完整示例:

    namespace ConsoleApplication4
     {
         /// <summary>
         /// 自定义一个枚举对象
         /// </summary>
        class ColorEnumerator : IEnumerator
         {
             private string[] _colors;
             private int _position = -1;
     
             public ColorEnumerator(string[] arr)
             {
                _colors = arr;
                 for (int i = 0; i < arr.Length; i++)
                 {
                    _colors[i] = arr[i];
                 }
             }
             public object Current
             {
                 get
                 {
                     if (_position == -1)
                     {
                         throw new InvalidOperationException();
                     }
                     if (_position >= _colors.Length)
                     {
                         throw new InvalidOperationException();
                     }
                      return _colors[_position];
                  }
              }
      
              public bool MoveNext()
              {
                 if (_position < _colors.Length - 1)
                 {
                      _position++;
                      return true;
                 }
                 else
                  {
                     return false;
                 }
             }
     
            public void Reset()
             {
                 _position = -1;
             }
         }
     
         /// <summary>
         /// 创建一个实现IEnumerable接口的枚举类
         /// </summary>
         class Spectrum : IEnumerable
         {
             private string[] Colors = { "red", "yellow", "blue" };
             public IEnumerator GetEnumerator()
             {
                 return new ColorEnumerator(Colors);
             }
         }
     
         class Program
         {
             static void Main(string[] args)
             {
                 Spectrum spectrum = new Spectrum();
                foreach (string color in spectrum)
                 {
                    Console.WriteLine(color);
                }
                Console.ReadKey();
             }
         }
    }
    View Code

    2、什么是枚举器?

    IEnumerable接口只有一个成员GetEnumerator方法,它返回的对象就是枚举器;实现了IEnumerator接口的枚举器包含三个函数成员:Current,MoveNext,Reset

    • Current是只读属性,它返回object类型的引用;
    • MoveNext是把枚举器位置前进到集合的下一项的方法,它返回布尔值,指示新的位置是否有效位置还是已经超过了序列的尾部;
    • Reset是把位置重置为原始状态的方法;

    3、为什么集合可以使用foreach来遍历

    我们知道当我们使用foreach语句的时候,这个语句为我们依次取出了数组中的每一个元素。

    例如下面的代码:

    int[] arr = { 1, 2, 3, 4, 5, 6 };
    foreach( int arry in arr )
    {
        Console.WriteLine("Array Value::{0}",arry);
    }

    输出效果为

    为什么数组可以使用foreach来遍历?原因是数组可以按需提供一个叫做枚举器(enumerator)的对象,枚举器可以依次返回请求的数组中的元素,枚举器知道项的次序并且跟踪它在序列中的位置。依次返回请求的当前项。

    对于有枚举器的类型而言,必须有一个方法来获取它这个类型。获取一个对象枚举器的方法是调用对象的GetEnumrator方法,实现GetEnumrator方法的类型叫做可枚举类型。那么数组就是可枚举类型。

    总结来说,实现GetEnumrator方法的类型叫做可枚举类型,GetEnumrator方法返回的对象就是枚举器,枚举器可以依次返回请求的数组中的元素,枚举器知道项的次序并且跟踪它在序列中的位置。依次返回请求的当前项。

    下图演示一下可枚举类型和枚举器之间的关系

    foreach结构设计用来和可枚举类型一起使用,只要给它的遍历对象是可枚举类型,比如数组。基本逻辑如下:

    • 通过调用GetEnumrator方法获取对象的枚举器。
    • 从枚举器中请求每一项并且把它作为迭代器,代码可以读取该变量,但不可以改变
    foreach(Type VarName in EnumrableObject )
    {
    }

    EnumrableObjec必须是可枚举类型。

    4、不用foreach能不能遍历各元素?

    当然是可以的,看下面代码:

     

    二、迭代器

    设计模式中有个迭代器模式,其实这里说的迭代器就是利用迭代器设计模式实现的一个功能,返回的是枚举器。

    1、自定义迭代器

    .net中迭代器是通过IEnumerable和IEnumerator接口来实现的,换句话说,使用迭代器设计模式实现了IEnumerable和IEnumerator,返回的是枚举器。今天我们也来依葫芦画瓢。首先来看看这两个接口的定义:

    并没有想象的那么复杂。其中IEnumerable只有一个返回IEnumerator的GetEnumerator方法。而IEnumerator中有两个方法加一个属性。接下来开发画瓢,我们继承IEnumerable接口并实现:

    下面使用原始的方式调用:

    有朋友开始说了,我们平时都是通过foreache来取值的,没有这样使用过啊。好吧,我们来使用foreach循环:

    为什么说基本上是等效的呢?我们先看打印结果,在看反编译代码。

    由此可见,两者有这么个关系:

    现在我们可以回答为什么在foreach中不能修改item的值?

    我们还记得IEnumerator的定义吗

     

    接口的定义就只有get没有set。所以我们在foreach中不能修改item的值。

    我们再来回答另一个问题:“要实现foreach需要满足什么条件?”:

    必须实现IEnumerable接口?NO

    我们自己写的MyIEnumerable删掉后面的IEnumerable接口一样可以foreach(不信?自己去测试)。

    所以要可以foreach只需要对象定义了GetEnumerator无参方法,并且返回值是IEnumerator或其对应的泛型。细看下图:

    也就是说,只要可以满足这三步调用即可。不一定要继承于IEnumerable。有意思吧!下次面试官问你的时候一定要争个死去活来啊,哈哈!

    2、yield的使用

    你肯定发现了我们自己去实现IEnumerator接口还是有些许麻烦,并且上面的代码肯定是不够健壮。对的,.net给我们提供了更好的方式。

    你会发现我们连MyIEnumerator都没要了,也可以正常运行。太神奇了。yield到底为我们做了什么呢?

    好家伙,我们之前写的那一大坨。你一个yield关键字就搞定了。最妙的是这块代码:

    这就是所谓的状态机吧!

    我们继续来看GetEnumerator的定义和调用:

    我们调用GetEnumerator的时候,看似里面for循环了一次,其实这个时候没有做任何操作。只有调用MoveNext的时候才会对应调用for循环:

    现在我想可以回答你“为什么Linq to Object中要返回IEnumerable?”:

    因为IEnumerable是延迟加载的,每次访问的时候才取值。也就是我们在Lambda里面写的where、select并没有循环遍历(只是在组装条件),只有在ToList或foreache的时候才真正去集合取值了。这样大大提高了性能。

    如:

    这个时候得到了就是IEnumerable对象,但是没有去任何遍历的操作。(对照上面的gif动图看)

    什么,你还是不信?那我们再来做个实验,自己实现MyWhere:

    现在看到了吧。执行到MyWhere的时候什么动作都没有(返回的就是IEnumerable),只有执行到ToList的时候才代码才真正的去遍历筛选。

    这里的MyWhere其实可以用扩展方法来实现,提升逼格。(Linq的那些查询操作符就是以扩展的形式实现的)

    3、怎样高性能的随机取IEnumerable中的值

     

     

    三、IEnumrator接口

    IEnumrator接口包含了3个函数成员:Current、MoveNext以及Reset;

    .Current是返回序列中当前位置项的属性。(注意:Current它是只读属性,它返回Object类型的引用,所以可以返回任意类型)

    .MoveNext是把枚举器位置前进到集合中下一项的方法。它也但会布尔值,指示新的位置是否是有效位置。

    注:如果返回的位置是有效的,方法返回true;

      如果新的位置是无效的,方法返回false;

      枚举器的原始位置在序列中的第一项之前,因此MoveNext必须在第一次使用Current之前调用。

    .Reset是把位置重置为原始状态的方法。

     下面我们用图表示一下他们之间的关系

     

    有了集合的枚举器,我们就可以使用MoveNext和Current成员来模仿foreach循环遍历集合中的项,例如,我们已经知道数组是可枚举类型,所以下面的代码手动做foreach语句

    自动做的事情。

    代码如下:

    复制代码
     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Text;
     5 using System.Threading.Tasks;
     6 using System.Collections;
     7 
     8 namespace ConsoleApplication1
     9 {
    10     class Program
    11     {
    12         static void Main(string[] args)
    13         {
    14             int[] arr = { 1, 2, 3, 4, 5, 6 };
    15             IEnumerator ie = arr.GetEnumerator();
    16             while( ie.MoveNext() )
    17             {
    18                 int i = (int)ie.Current;
    19                 Console.WriteLine("{0}", i);
    20             }
    21         }
    22     }
    23 }
    复制代码

    程序运行的结果为

    我们来用图解释一下代码中的数组结构

    IEnumerable接口

    数组是可枚举类型,是因为实现了IEnumerable接口的类,所以可枚举类都是因为实现了IEnumerable接口的类。

    IEnumerable接口只有一个成员——GetEnumerator()方法,它返回对象的枚举器。

    如图所示:

    下面我们举一个使用IEnumerator和IEnumerable的例子

    下面的代码展示了一个可枚举类的完整示例,该类叫Component(球形)。它的枚举器类为Shape(形状)。

    代码如下:

    复制代码
     1 using System;
     2 using System.Collections;
     3 
     4 namespace ConsoleApplication1
     5 {
     6     class Shape : IEnumerator
     7     {
     8         string[] _Shapes;
     9         int _Position = -1;
    10 
    11         public Shape(string[] _theShapes)
    12         {
    13             _Shapes = new string[_theShapes.Length];
    14             for( int i = 0; i < _theShapes.Length; i++ )
    15             {
    16                 _Shapes[i] = _theShapes[i];
    17             }
    18         }
    19 
    20         public Object Current
    21         {
    22             get
    23             {
    24                 if ( _Position == -1 )
    25                     throw new InvalidOperationException();
    26                 if (_Position >= _Shapes.Length)
    27                     throw new InvalidOperationException();
    28                 return _Shapes[_Position];
    29             }
    30         }
    31 
    32         public bool MoveNext()
    33         {
    34             if (_Position < _Shapes.Length - 1)
    35             {
    36                 _Position++;
    37                 return true;
    38             }
    39             else
    40                 return false;
    41         }
    42 
    43         public void Reset()
    44         {
    45             _Position = -1;
    46         }
    47     }
    48 
    49     class Component : IEnumerable
    50     {
    51         string[] shapes = { "Circular", "spherical", "Quadrilateral", "Label" };
    52         public IEnumerator GetEnumerator()
    53         {
    54             return new Shape( shapes );
    55         }
    56     }
    57 
    58     class Program
    59     {
    60         static void Main(string[] args)
    61         {
    62             Component comp = new Component();
    63             foreach ( string oshape in comp )
    64                 Console.WriteLine(oshape);
    65         }
    66        
    67     }
    68 }
    复制代码

    运行结果:

     

    C#图解教程 第十八章 枚举器和迭代器

    https://www.cnblogs.com/qtiger/p/13571909.html

  • 相关阅读:
    opennebula 编译日志
    eclipse scons 使用指南
    eclipse 安装scons
    在Windows7上搭建Cocos2d-x 3.2alpha0开发环境
    Centos6.3 jekyll环境安装
    CNN-利用1*1进行降维和升维
    偏导数
    卷积神经网络--CNN
    struts2 模型驱动
    Struts2 数据驱动
  • 原文地址:https://www.cnblogs.com/qtiger/p/13609206.html
Copyright © 2011-2022 走看看