02011901 枚举器和迭代器01-枚举器(IEnumerator)、可枚举类型(IEnumerable)、泛型枚举接口

02011901 枚举器和迭代器01-枚举器(IEnumerator)、可枚举类型(IEnumerable)、泛型枚举接口

1. 枚举器和可枚举类型

1.1 什么是枚举器
// 使用foreach语句读取数组中每一个元素
using System;

namespace Demo01
{
    class Program
    {
        static void Main()
        {
            int[] arr = { 10, 20, 30, 40 };
            foreach(int item in arr)
                Console.WriteLine($"{item}");

            Console.ReadLine();
        }
    }
}

控制台输出:
10
20
30
40

注意,数组能使用循环遍历的原因:
1. 数组可以按需提供一个叫做枚举器的对象。
2. 枚举器可以依次返回请求的数组中的元素。
3. 枚举器知道项的次序并且跟踪它在序列中的位置,然后返回请求的当前项。
1.2 可枚举类型
图片链接丢失
  • 对于有枚举器的类型而言,必须有一种方法来获取它。获取枚举器的方法是调用对象的GetEnumerator方法。实现GetEnumerator方法的类型叫做可枚举类型。
  • 可枚举类型 → 是带有GetEnumerator方法的类型,它返回用于项的枚举器。
  • 枚举器 → 是可以依次返回集合中项的类对象。
1.3 foreach结构设计
  • foreach结构设计用来和可枚举类型一起使用,只要给它的遍历对象是可枚举类型,比如数组,它就会执行如下行为。
    • 通过调用GetEnumerator方法获取可枚举类型的枚举器。
    • 从枚举器中请求每一项并且把它作为迭代变量,代码可以读取该变量但不可以改变。
                        必须是可枚举类型
                               ↓
foreach(Type VarName in EnumerableObject)

2. IEnumerator接口

  • 实现了IEnumerator接口的枚举器包含3个函数成员。
    • Current → 返回序列中当前位置项的属性。
      • 它是只读属性
      • 它返回object类型的引用,所以可以返回任何类型的对象。
    • MoveNext → 是把枚举器位置前进到集合中下一项的方法,它返回布尔值,指示新的位置是有效位置还是已经超过了序列的尾部。
      • 如果新的位置是有效的,方法返回true。
      • 如果新的位置是无效的(比如当前位置到达了尾部),方法返回false。
      • 枚举器的原始位置在序列中的第一项之前,因此MoveNext必须在第一次使用Current之前调用。
    • Reset → 是把位置重置为原始状态的方法。

3. IEnumerable接口

  • 可枚举类是指实现了IEnumerable接口的类。
    • IEnumerable接口只有一个成员,即GetEnumerator方法,它返回对象的枚举器。
图片链接丢失
GetEnumerator方法返回类的一个枚举器对象
// 如上图所示,演示了一个有3个枚举项的类MyClass,通过实现GetEnumerator方法来实现IEnumerable接口。
using System.Collections;
		  实现IEnumerable接口
                    ↓
class MyClass : IEnumerable
{
    public IEnumerator GetEnumerator {...}
               ↑
     返回IEnumerator类型的对象
    ...
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// 一个可枚举类的示例
using System.Collections;

class MyColors : IEnumerable
{
    string[] Colors = {"Red", "Yellow", "Blue"};
    
    public IEnumerator GetEnumerator()
    {
        return new ColorEnumerator(Colors);
                         ↑
                    枚举器类的实例
    }
}

4. 使用IEnumerable和IEnumerator的示例

using System;
using System.Collections;

namespace Demo01
{
    class ColorEnumerator : IEnumerator // @1 通过实现IEnumerator接口,定义一个枚举器类型。
    {
        string[] colors;
        int position = -1;

        public ColorEnumerator(string[] theColors) // @1.1 构造函数传入一个string类型的数组。
        {
            colors = new string[theColors.Length];
            for(int i = 0; i < theColors.Length; i++) 
                colors[i] = theColors[i]; // @1.2 将传入的数组每个元素添加到新数组当中。
        }

        public object Current // @1.4 实现IEnumerator接口只读的属性。
        {
            get
            {
                if(position == -1) 
                    throw new InvalidCastException();
                if (position >= colors.Length)
                    throw new InvalidOperationException();

                return colors[position];
            }
        }

        public bool MoveNext() // @1.3 实现IEnumerator接口的方法,MoveNext在第一次使用Current之前调用。
        {
            if (position < colors.Length - 1)
            {
                position++;
                return true;
            }
            else
                return false;
        }

        public void Reset() // @1.5 实现IEnumerator接口的方法。
        {
            position = -1;
        }
    }

    class Spectrum : IEnumerable // @2 实现IEnumerable接口,定义一个可枚举类型。
    {
        string[] Colors = { "Violet", "Blue", "Green", "Yellow", "Orange" };

        public IEnumerator GetEnumerator() // @2.1 注意,返回类型为接口。此时可以返回实现该接口类型的对象。
        {
            return new ColorEnumerator(Colors); // @2.2 返回ColorEnumerator类型的对象。
        }
    }
    class Program
    {
        static void Main()
        {
            Spectrum st = new Spectrum();
            foreach(string color in st) // @3 注意,st必须是可枚举类型。
                Console.WriteLine(color);

            Console.ReadLine();
        }
    }
}

控制台输出:
Violet
Blue
Green
Yellow
Orange

说明:
1. 使用foreach时,会自动调用可枚举类型的GetEnumerator方法。
2. 在@1处定义的是一个枚举器类型。该类型实现了IEnumerator,并实现了三个方法,其中MoveNext方法必须在第一次使用Current方法之前调用。
3. 在@2处定义的是一个可枚举类型。该类型实现了IEnumerable接口,并且实现了该接口唯一的方法GetEnumerator。

5. 泛型枚举接口

  • 目前我们描述的接口都是非泛型版本,实际上大多数情况下开发人员应该使用泛型版本的IEnumerable和IEnumerator

  • IEnumerable、IEnumerator这两个接口泛型和非泛型版本的本质差别如下。

    • 对于非泛型形式。
      • IEnumerable接口的GetEnumerator方法返回实现IEnumerator的枚举器类实例。
      • 实现IEnumerator的类实现了Current属性,它返回object类型的引用,然后我们必须把它转换为对象的实际类型。
    • 泛型接口继承自非泛型接口,对于泛型接口形式。
      • IEnumerable接口的GetEnumerator方法返回实现IEnumator的枚举类的实例。
      • 实现IEnumerator的类实现了Current属性,它返回实际类型的实例,而不是object基类的引用(※※※)。
      • 这些是协变接口,所以它们的实际声明就是IEenumerable和IEnumerator,这意味着实现这些接口的对象可以是派生的类型。
  • 需要注意的是,我们目前所看到的非泛型接口的实现不是类型安全的。它们返回object类型的引用,然后必须转换为实际类型。

    • 而泛型接口的枚举器是类型安全的,它返回实际类型的引用。

    • 如果要创建自己的可枚举类,应该实现这些泛型接口。非泛型的版本可用于C# 2.0以前没有泛型的遗留代码。

  • 尽管泛型版本和非泛型版本一样简单易用,但泛型版本结构略显复杂。

Typora-Logo
实现IEnumerator接口的类的结构

实现IEnumerable接口的类的结构

结尾

书籍:C#图解教程

著:【美】丹尼尔 · 索利斯;卡尔 · 施罗坦博尔

译:窦衍森;姚琪琳

ISBN:978-7-115-51918-4

版次:第5版

发行:人民邮电出版社

※敬请购买正版书籍,侵删请联系85863947@qq.com※

※本文章为看书或查阅资料而总结的笔记,仅供参考,如有错误请留言指正,谢谢!※

posted @ 2025-08-19 23:34  qinway  阅读(27)  评论(0)    收藏  举报