• 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

  • 相关阅读:
    CSS浮动(float、clear)通俗讲解
    JAVA 类的加载
    数据库操作 delete和truncate的区别
    正则表达式 匹配相同数字
    Oracle EBS OM 取消订单
    Oracle EBS OM 取消订单行
    Oracle EBS OM 已存在的OM订单增加物料
    Oracle EBS OM 创建订单
    Oracle EBS INV 创建物料搬运单头
    Oracle EBS INV 创建物料搬运单
  • 原文地址:https://www.cnblogs.com/qtiger/p/13609206.html
Copyright © 2020-2023  润新知