枚举和迭代器

本文深入解析了枚举器(enumerator)的概念,包括其如何通过GetEnumerator方法使数组成为可枚举类型,以及IEnumerator和IEnumerable接口的实现细节。同时,文章介绍了C# 2.0版本中引入的迭代器,探讨了如何使用迭代器简化枚举类型和枚举器的创建,并解释了迭代器的实质和使用方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 枚举器
为什么数组可以使用 foreach 输出各元素?

这是因为数组是 枚举类型(enumerable) ,它通过 GetEnumerator 方法提供一个 枚举器(enumerator),即能实现 GetEnumerator 方法的类型是 枚举类型
枚举器可以依次返回请求的数组的元素,枚举器知道 项的次序 并跟踪它的序列位置,然后返回 请求的当前项
对象枚举类型获取枚举器的方法是调用对象的 GetEnumerator 方法
1.1 Ienumerable 接口
可枚举类可以是 实现 Ienumerable 接口的类型,而 Ienumberable 接口只有一个成员,那就是 GetEnumerator 方法,它返回对象的 枚举器

using System.Collections;

// 实现 Ienumerable 接口
class MyColors: IEnumerable
{
    string[] Colors = {"Red", "Yellow", "Blue"};
    public IEnumerator GetEnumerator()
    {
        return new ColorEnumerator(); // 注意上方返回的类型是 IEnumerator,所以这里的返回对象引用会自动转换为它们实现的接口引用
    }
}

1.2 Ienumerator 接口
实现 Ienumerator 接口的枚举器,它包含三个方法:

Current:返回序列中当前位置项,属于一个只读 属性
MoveNext:把枚举器的位置移动到集合中下一项的方法,返回一个布尔值,有下一项返回 true;另外 枚举器的原始位置在第一项之前(如 -1),即在使用 Current 之前,必须要先调用 MoveNext 方法
Reset:把枚举器的位置重置到初始状态的方法
至此,我们也可以写出类如 foreach 语句的功能,如:

static void Main()
{
    int[] MyArray = {10, 11, 12, 13};
    IEnumerator ie = MyArray.GetEnumerator(); // 实现接口,对象引用能自动转换为它们实现的接口引用
    while (ie.MoveNext())
    {
        int i = (int) ie.Current;
        Console.WriteLine("{0}", i);
    }
}

使用 IEnumerable 和 IEnumerator 的实例

using System;
using System.Collections;

// 创建枚举器
class ColorEnumerator : IEnumerator
{
    string[] _colors; // 用于存储内部的 color 数组
    int _position = -1; // 用于表示枚举器位置

    // 构造函数,用于创建 _color 数组
    public ColorEnumerator(string[] theColors)
    {
        _colors = new string[theColors.Length];
        for (int i = 0; i < theColors.Length; i++)
        {
            _colors[i] = theColors[i];
        }
    }

    // 实现 Current
    public object Current
    {
        get
        {
            if (_position == -1)
            {
                throw new InvalidOperationException();
            }
            if (_position >= _colors.Length)
            {
                throw new InvalidOperationException();
            }
            return _colors[_position];
        }
    }

    // 实现MoveNext
    public bool MoveNext()
    {
        if (_position < _colors.Length - 1)
        {
            _position++;
            return true;
        }
        else
        {
            return false;
        }
    }

    // 实现 Reset
    public void Reset()
    {
        _position = -1;
    }
}

// 枚举类型,实现 IEnumrable 接口,提供 GetEnumerator
class Sectrum : IEnumerable
{
    string[] Colors = { "violet", "blue", "cyan", "green" };
    public IEnumerator GetEnumerator()
    {
        return new ColorEnumerator(Colors);
    }
}

// 主流图
class Program
{
    static void Main()
    {
        Sectrum spectrum = new Sectrum();
        foreach (string color in spectrum)
        {
            Console.WriteLine(color);
        }
    }
}

1.3 泛型枚举接口
在大多数情况下,都应该使用泛型版本的 IEnumerator 和 IEnumerable,泛型与非泛型二者的本质都差不多。
对于非泛型而言:

IEnumerable 接口的 GetEnumerator 方法返回实现 IEnumerator 枚举类的实例
实现 IEnumerator 的类实现了 Current 属性,它返回 object 的引用,然后我们必须把它转换为实际类型的对象
对于泛型:

IEnumerable 接口的 GetEnumerator 方法返回实现 IEnumator 枚举器类的实例
实现 IEnumerator 的类实现了 Current 属性,它返回 实际类型的对象,而不是 object 基类的引用
2 迭代器
C# 在2.0版本开始提供更简单的创建 枚举类型 和 枚举器 的方式,而实际上是 编译器为我们创建它们,这种结构叫做 迭代器,例如:

// 迭代器返回一个泛型枚举器
// 注意由迭代器返回的枚举器并不能完全实现 IEnumerator 接口
// 它没有实现 Reset 方法

public IEnumerator<string> BlackAndWhite()
{
    yield return "black";
    yield return "gray";
    yield return "white";
}

2.1 迭代器块
迭代器块 是有一个或多个 yield 语句的 代码块,下面任意一种都可以是 迭代器块:

方法主体
访问器主体
运算符主体
迭代器块 与 其他代码块 不一样:

其他代码块 包含的语句被当做是 命令式,即先执行代码块的第一个语句,然后执行后面的语句,最后控制离开块
迭代器块 不是 需要在同一时间执行的一串命令式命令,而是 描述了希望编译器为我们创建的 枚举器 或 枚举类 的行为
yield return 语句指定序列中返回的下一项
yield break 语句指定在序列中 没有 其他项
2.2 使用迭代器来创建枚举器

// 并没有实现 IEnumerable 接口
class MyClass
{
    // 实现 GetEnumerator 方法
    public IEnumerator<string> GetNnumerator()
    {
        return BlackAndWhite(); // 返回迭代器生成的泛型枚举器
    }

    // 迭代器返回泛型枚举器(字符串)
    public IEnumerator<string> BlackAndWhite()
    {
        yield return "black";
        yield return "gray";
        yield return "white";
    }
}

class Program
{
    static void Main()
    {
        Myclass mc = new MyClass();
        foreach (string shade in mc) 
        {
            Console.WriteLine(shade);
        }
    }
}

其中要说明的是

foreach 只要实现了 GetEnumerator 方法就可以使用 foreach 方法进行遍历
代码中的迭代器产生了一个枚举器,这个枚举器(Enumerator)有一个 嵌套类 实现了 IEnumerator
2.3 使用迭代器来创建可枚举类型

class MyClass
{
    public IEnumerator<string> GetEnumerator()
    {
        IEnumerable<string> myEnumerable = BlackAndWhite();
        return myEnumerable.GetEnumerator();
    }
    public IEnumerable<string> BlackAndWhite()
    {
        yield return "black";
        yield return "gray";
        yield return "white";
    } 
}

class Program
{
    static void Main()
    {
        MyClass mc = new MyClass();
        // 使用类对象
        foreach (string shade in mc)
        {
         Console.Write("{0}", shade);
        }
        // 使用类方法
        foreach (string shade in mc.BlackAndWhite())
        {
         Console.Write("{0}", shade);
        }
    }
}

其中编译器生成的类实现了 IEnumerator 也实现了 IEnumerable

2.4 迭代器的实质
迭代器需要 System.Collection.Generic 命名空间,因此我们需要使用 using 指令引入它

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值