C#图解教程笔记17-枚举器和迭代器

19.1 枚举器和可枚举类型

19.1.1 枚举器的概述

我们可以使用foreach语句遍历数组中的元素,什么数组可以这么做?原因是数组可以按需提供一个叫作枚举器的对象,枚举器可以依次返回请求的数组中的元素。

对于有枚举器的类型而言,必须有一种方法来获取它。获取对象举器的方法是调用对象的GetEnumerator方法,实现该方法的类型叫做可枚举类型。

foreach结构设计用来和可枚举类型一起使用。只要给它的遍历对象是可枚举类型,比如数组,它就会执行如下行为:①通过调用GetEnumerator方法获取对象的枚举器 ②从枚举器中请求每一项并且把它作为迭代变量,代码可以取该变量但不可以改变


19.1.2 IEnumerator接口

实现了 IEnumerator接口的枚举器包含3个函数成员:Current、MoveNext 以及 Reset。

①current是返回序列中当前位置项的属性,它是只读属性,它返回object类型的引用,所以可以返回任何类型的对象。

②MoveNext 是把枚举器位置前进到集合中下一项的方法。它也返回布尔值,指示新的位置是有效位置还是已经越界:如果新的位置是有效的,方法返回true,否则返回false;枚举器的原始位置在序列中的第一项之前,因此MoveNext必须在第一次使用Current之前调用。

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

枚举器跟踪序列中当前项的方式完全取决于实现。可以通过对象引用、索引值或其他方式来实现。对于内置的一维数组来说,就使用项的索引。


19.1.3 IEnumerable接口

可枚举类是指实现了IEnumerable接口的类。IEnumerable接口只有一个成员GetEnumerator方法,它返回对象的枚举器。


19.1.4 示例

cs 复制代码
class ColorEnumerator : IEnumertor
{
    string[] colors;
    int position = -1;

    public ColorEnumerator(string[] theColors)
    {
        colors = new string[theColors.Length];
        for(int i = 0; i < theColor.Length; i++)
            colors[i] = theColor[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;
    }
}

class Spectrum : IEnumerable
{
    string[] Colors = {"violet", "blue", "cyan", "green", "yellow", "orange", "red"};
    public IEnumerator GetEnumerator()
    {
        return new ColorEnumerator(Colors);
    }
}

class Program
{
    static void Main()
    {
        Spectrum spectrum = new Spectrum();
        foreach(string color in spectrum)
            Console.WriteLine(color);
    }
}

19.1.5 泛型枚举接口

实际上我们大多使用泛型版本的IEnumerator<T>和IEnumerable<T>,他们的主要区别如下所示:

对于非泛型接口形式:①lEnumerable接口的 GetEnumerator 方法返回实现IEnumerator 的枚举器类实例 ②实现IEnumerator的类实现了Current属性,它返回object类型的引用,然后我们必须把它转化为对象的实际类型。

泛型接口继承自非泛型接口,对于泛型接口形式:①IEnumerable<T>接口的 GetEnumerator方法返回实现IEnumerator<T>的枚举器类的实例 ②实现IEnumerator<T>的类实现了Current属性,它返回实际类型的实例,而不是object基类的引用 ③这些是协变接口,所以它们的实际声明就是IEnumerable<out T>和IEnumerator<out T>

需要重点注意的是,我们目前所看到的非泛型接口的实现不是类型安全的。它们返回object类型的引用,然后必须转化为实际类型。而泛型接口的枚举器是类型安全的,它返回实际类型的引用。如果要创建自己的可枚举类应该实现这些泛型接口。


19.2 迭代器

19.2.1 概述

在 C# 中,迭代器(Iterator) 是一种简化集合遍历逻辑的机制,它允许开发者自定义序列的生成和遍历方式,而无需手动实现复杂的IEnumerable/IEnumerator接口。迭代器的核心是yield关键字,结合 C# 的状态机编译特性,极大地降低了遍历逻辑的实现成本

迭代器的本质是实现了遍历逻辑的代码块 ,它能够生成一个序列,并支持逐个取出序列中的元素。在 C# 中,迭代器主要用于:①让自定义类型支持foreach循环遍历 ②动态生成序列(如无限序列、分页序列、计算型序列) ③实现 LINQ(Language Integrated Query)的延迟执行特性(LINQ to Objects 的核心就是迭代器)

传统上,若要让一个类支持foreach,需手动实现IEnumerable/IEnumerator接口,管理迭代状态(如当前索引、遍历完成标记等),代码繁琐且易出错。迭代器通过yield关键字将这一过程自动化,开发者只需关注序列的生成逻辑即可。

19.2.2 迭代器的核心接口

接口 核心成员 作用
IEnumerable IEnumerator GetEnumerator() 表示一个可枚举的集合,提供获取迭代器的方法。
IEnumerator object Current { get; }bool MoveNext()void Reset() 表示一个迭代器,负责遍历集合的元素,管理迭代状态。
IEnumerable<T> 继承IEnumerable,新增IEnumerator<T> GetEnumerator() 泛型可枚举接口,避免装箱 / 拆箱,类型安全。
IEnumerator<T> 继承IEnumeratorIDisposable,新增T Current { get; } 泛型迭代器,支持类型安全的元素访问和资源释放(Dispose)。

IDisposable:泛型迭代器实现IDisposable,确保迭代过程中的资源(如using块、数据库连接)被正确释放。

19.2.3 yield关键字

yield关键字是 C# 迭代器的核心,用于定义迭代器块(Iterator Block),分为两种形式:①yield return <表达式>表示将表达式的结果作为序列的下一个元素返回,执行到该语句时,迭代器会暂停执行 ,并保存当前状态;下次调用MoveNext()时,从暂停处继续执行。 ②yield break表示立即终止序列,迭代器的MoveNext()将返回false,后续代码不再执行。

迭代器方法(或属性、索引器)需满足以下条件:

返回类型 :必须是IEnumerableIEnumerable<T>IEnumeratorIEnumerator<T>中的一种(泛型版本优先使用)

方法体 :包含yield returnyield break(仅包含yield break时生成空序列)

修饰符 :不能是async(C# 8.0 后支持异步迭代器,使用IAsyncEnumerable<T>)、ref/out参数,也不能是void返回类型。

19.2.4 迭代器的工作原理

C# 编译器会将包含yield的迭代器块转换为一个私有嵌套类 (状态机),该类实现了对应的IEnumerable<T>/IEnumerator<T>接口,并管理迭代的状态。状态机的核心逻辑包括:

状态字段 :一个int类型的字段(如_state),表示迭代器的当前执行位置(初始值为 - 1,0 表示未开始,1、2... 表示暂停后的位置,-2 表示完成)

当前元素字段 :存储Current属性的值(如_current

MoveNext () 方法 :根据_state的值执行对应的代码块,直到遇到yield return(设置_current_state更新为当前位置,返回true)或yield break_state设为 - 2,返回false) ④Dispose () 方法 :清理迭代器的状态(如释放using块中的资源、恢复_state为 - 2)

19.2.5 迭代器的典型应用场景

①自定义集合类的遍历:让自定义集合支持foreach循环,只需实现IEnumerable<T>接口,并通过迭代器方法实现GetEnumerator(),无需手动编写迭代器类。

cs 复制代码
public class Node<T>
{
    public T Value { get; set; }
    public Node<T> Next { get; set; }
    public Node(T value) => Value = value;
}

public class MyLinkedList<T> : IEnumerable<T>
{
    private Node<T> _head;
    
    public void Add(T value)
    {
        if (_head == null)
        {
            _head = new Node<T>(value);
            return;
        }
        Node<T> current = _head;
        while (current.Next != null) current = current.Next;
        current.Next = new Node<T>(value);
    }
    
    // 迭代器方法实现GetEnumerator()
    public IEnumerator<T> GetEnumerator()
    {
        Node<T> current = _head;
        while (current != null)
        {
            yield return current.Value; // 逐个返回节点值
            current = current.Next;
        }
    }
    
    // 非泛型版本(显式接口实现)
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

// 调用示例
var list = new MyLinkedList<int>();
list.Add(1);
list.Add(2);
foreach (int num in list) Console.WriteLine(num); // 输出1、2

②生成无限序列:由于迭代器是延迟执行的,可生成无限序列(如斐波那契数列、自然数序列),遍历时分批获取所需元素,避免内存溢出。

cs 复制代码
public static IEnumerable<long> Fibonacci()
{
    long a = 0, b = 1;
    while (true) // 无限循环
    {
        yield return a; // 延迟返回
        long temp = a + b;
        a = b;
        b = temp;
    }
}

// 调用:取前10个元素(LINQ的Take()也是延迟执行)
foreach (long num in Fibonacci().Take(10))
{
    Console.WriteLine(num); // 输出0、1、1、2、3、5、8、13、21、34
}

③分页数据处理:从数据库、文件或网络中分页读取数据时,迭代器可逐页加载数据,避免一次性加载所有数据到内存,降低内存占用。

cs 复制代码
// 模拟从数据库分页获取数据
public static List<DataRow> GetDataFromDB(int pageIndex, int pageSize)
{
    // 实际开发中使用ADO.NET/EFCore分页查询
    var data = new List<DataRow>();
    // 模拟数据:若pageIndex>3,返回空列表
    if (pageIndex <= 3)
    {
        for (int i = 0; i < pageSize; i++)
        {
            data.Add(new DataRow { Id = pageIndex * pageSize + i });
        }
    }
    return data;
}

// 迭代器:逐页读取数据
public static IEnumerable<DataRow> GetPagedData(int pageSize)
{
    int pageIndex = 1;
    while (true)
    {
        var pageData = GetDataFromDB(pageIndex, pageSize);
        if (pageData.Count == 0) yield break; // 无数据时终止
        foreach (var row in pageData) yield return row; // 返回当前页数据
        pageIndex++;
    }
}

// 调用:遍历所有分页数据
foreach (var row in GetPagedData(2))
{
    Console.WriteLine($"数据ID:{row.Id}"); // 输出0、1、2、3、4、5(共3页,每页2条)
}

// 模拟DataRow类
public class DataRow { public int Id { get; set; } }

④序列的组合与转换:迭代器可灵活组合、筛选或转换多个序列,实现类似 LINQ 的ConcatWhereSelect等功能。

cs 复制代码
public static IEnumerable<T> Concat<T>(IEnumerable<T> seq1, IEnumerable<T> seq2)
{
    foreach (T item in seq1) yield return item; // 先遍历第一个序列
    foreach (T item in seq2) yield return item; // 再遍历第二个序列
}

// 调用
var seq1 = new List<int> { 1, 2 };
var seq2 = new List<int> { 3, 4 };
foreach (int num in Concat(seq1, seq2)) Console.WriteLine(num); // 输出1、2、3、4

⑤LINQ to Objects 的底层实现:LINQ to Objects 的大部分方法(如WhereSelectTakeSkipGroupBy等)都是通过迭代器实现的,这也是 LINQ 支持延迟执行 的核心原因。例如,Where方法的简化实现

cs 复制代码
public static IEnumerable<TSource> Where<TSource>(
    this IEnumerable<TSource> source,
    Func<TSource, bool> predicate)
{
    foreach (TSource item in source)
    {
        if (predicate(item)) yield return item; // 满足条件时返回元素
    }
}
相关推荐
_落纸1 小时前
《传感器与检测技术》第 4 章 光电式传感器原理与应用
笔记·自动化
兜兜转转了多少年2 小时前
《Prompt Engineering白皮书》笔记04 System / Context / Role 三种提示工程
人工智能·笔记·prompt
BlackWolfSky2 小时前
ES6 学习笔记3—7数值的扩展、8函数的扩展
前端·javascript·笔记·学习·es6
Oll Correct2 小时前
Excel基础操作(四)
笔记·excel
摇滚侠2 小时前
ElasticSearch 教程入门到精通,核心概念,系统架构,单节点集群,故障转移,水平扩容,笔记33、34、35、36、37
笔记·elasticsearch·系统架构
Zzz 小生3 小时前
Github-Langflow:可视化AI工作流构建平台,让AI应用开发更简单
人工智能·笔记·python·github
chase。3 小时前
【学习笔记】线性复杂度微分逆运动学:增广拉格朗日视角深度解析
人工智能·笔记·学习
摇滚侠3 小时前
ElasticSearch 教程入门到精通,部署环境,Windows 集群部署,笔记29、30
大数据·笔记·elasticsearch
YJlio3 小时前
Active Directory 工具学习笔记(10.5):AdInsight 数据捕获与显示选项——把噪声压下去,把关键抬上来
人工智能·笔记·学习