C#进阶学习(十六)C#中的迭代器

目录

一、什么是迭代器

二、标准迭代器是怎么写的

实现步骤:

[三、如何利用yield return语法糖简化迭代器的写法](#三、如何利用yield return语法糖简化迭代器的写法)

四、迭代器的使用示例


引言

在C#编程中,遍历集合是高频操作,而迭代器作为这一过程的核心机制,通过封装遍历逻辑实现了代码的简洁性与复用性。无论是数组、列表还是自定义集合,foreach循环的背后都依赖于迭代器模式。理解迭代器的底层实现与高级特性,不仅能优化代码结构,还能为处理复杂数据场景(如延迟加载、海量数据分块遍历)提供灵活方案。本文从基础实现到现代语法糖,系统解析C#迭代器的核心机制与应用实践。

一、什么是迭代器

迭代器(iterator) 有时又称光标(cursor)

是程序设计的软件设计模式

迭代器模式提供了一个方法顺序访问了一个聚合对象的各个元素

而又不暴露其内部标识

在表现效果上看

是可以在容器对象(例如链表或数组)上遍历访问的接口

设计人员无需关心容器对象的内存分配实现的细节

可以用foreach遍历的类,都是实现了迭代器的

在 C# 中,迭代器是一种用于遍历集合元素 的设计模式。当我们使用 foreach 循环遍历数组或集合时:

cs 复制代码
string[] names = { "Alice", "Bob", "Charlie" };
foreach (var name in names)
{
    Console.WriteLine(name);
}

foreach 背后实际是通过以下两个接口实现的:

IEnumerable:标识对象可被迭代

IEnumerator:提供具体的迭代能力

迭代器就是实现了这些接口的对象,它封装了遍历集合的细节,使得遍历过程与集合结构解耦。

二、标准迭代器是怎么写的

实现步骤:
  1. 实现 IEnumerable 接口

  2. 创建嵌套的 IEnumerator 实现类

  3. 维护迭代状态(如当前位置索引)

  4. 实现 MoveNext() 和 Reset() 方法

  5. 通过 Current 属性返回当前元素

cs 复制代码
// 1. 实现IEnumerable接口,声明这是一个可迭代集合
public class Bookshelf : IEnumerable
{
    private string[] _books = { "C#入门", "设计模式", "算法导论" };

    // 2. IEnumerable的核心方法:返回迭代器对象
    public IEnumerator GetEnumerator()
    {
        // 创建自定义迭代器实例,传入要遍历的数据
        return new BookshelfEnumerator(_books);
    }

    // 3. 定义私有嵌套类实现IEnumerator(隐藏实现细节)
    private class BookshelfEnumerator : IEnumerator
    {
        private readonly string[] _books; // 要遍历的数据源
        private int _currentIndex = -1;   // 迭代游标(初始位置在第一个元素之前)

        // 4. 构造函数:接收待遍历的集合
        public BookshelfEnumerator(string[] books)
        {
            _books = books; // 绑定数据源
        }

        // 5. MoveNext():推进到下一个元素(核心逻辑)
        public bool MoveNext()
        {
            _currentIndex++; // 游标移动
            // 判断是否超出集合范围
            return _currentIndex < _books.Length;
        }

        // 6. Reset():重置迭代器(注意:foreach循环不会调用此方法)
        public void Reset()
        {
            _currentIndex = -1; // 重置游标到初始位置
        }

        // 7. Current:获取当前元素(重点注意边界检查)
        public object Current
        {
            get
            {
                // 有效性检查(避免越界访问)
                if (_currentIndex == -1 || _currentIndex >= _books.Length)
                    throw new InvalidOperationException("迭代器不在有效位置");
                
                return _books[_currentIndex];
            }
        }
    }
}

好的,接下来怎么来实现一个泛型的数据类的迭代器:

cs 复制代码
using System;
using System.Collections;
using System.Collections.Generic;

// 泛型集合类实现IEnumerable<T>
public class GenericCollection<T> : IEnumerable<T>
{
    private readonly T[] _items;

    public GenericCollection(T[] items) => _items = items;

    // 实现泛型版本的GetEnumerator
    public IEnumerator<T> GetEnumerator() => new GenericEnumerator(_items);

    // 显式实现非泛型接口(规范要求)
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    // 嵌套泛型迭代器类
    private class GenericEnumerator : IEnumerator<T>
    {
        private readonly T[] _items;
        private int _index = -1;

        public GenericEnumerator(T[] items) => _items = items;

        // 实现MoveNext(与非泛型版本逻辑相同)
        public bool MoveNext() => ++_index < _items.Length;

        public void Reset() => _index = -1;

        // 泛型Current属性(避免装箱拆箱)
        public T Current 
        {
            get
            {
                if (_index < 0 || _index >= _items.Length)
                    throw new InvalidOperationException();
                return _items[_index];
            }
        }

        // 显式实现非泛型Current(接口要求)
        object IEnumerator.Current => Current!;

        // 实现IDisposable(泛型迭代器必须实现)
        public void Dispose() 
        {
            /* 若无需要释放的资源可留空 */
        }
    }
}

三、如何利用yield return语法糖简化迭代器的写法

看了看上面的繁杂写法,聪明的你肯定想,会不会有大佬封装啊,简化写法啊,回答是,有的,有的。

yield return 是C# 2.0引入的语法糖

所谓语法糖 也称语法糖衣

主要作用就是将复杂逻辑简单化,可以增加程序的可读性

从而减少程序代码出错的机会

关键接口 :IEnumerable

命名空间:System.Collections

让想要通过Foreach遍历的自定义实现接口中的方法GetEnumerator()即可

yield return 是 C# 提供的语法糖,可以自动生成迭代器状态机,无需手动实现完整迭代器:

如下面所示:是不是就很便捷的实现了刚才我们需要的功能!

cs 复制代码
public class Bookshelf : IEnumerable
{
    private string[] _books = { "C#入门", "设计模式", "算法导论" };

    public IEnumerator GetEnumerator()
    {
        for (int i = 0; i < _books.Length; i++)
        {
            // 编译器自动生成状态机
            yield return _books[i];
        }
    }
}

利用语法糖实现的泛型类:

cs 复制代码
using System.Collections.Generic;

public class GenericCollectionWithYield<T> : IEnumerable<T>
{
    private readonly T[] _items;

    public GenericCollectionWithYield(T[] items) => _items = items;

    // 使用yield return自动生成迭代器
    public IEnumerator<T> GetEnumerator()
    {
        foreach (var item in _items)
        {
            // 编译器自动生成状态机
            yield return item;
        }
    }

    // 显式实现非泛型接口
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
}

四、迭代器的使用示例

cs 复制代码
// 创建整型集合
var intCollection = new GenericCollection<int>(new[] { 1, 2, 3 });
foreach (var num in intCollection)
{
    Console.WriteLine(num * 2); // 输出2,4,6
}

// 使用yield版本
var stringCollection = new GenericCollectionWithYield<string>(
    new[] { "A", "B", "C" });
foreach (var s in stringCollection)
{
    Console.WriteLine(s + "!"); // 输出A!, B!, C!
}

可以看出,实际上的使用并无任何区别,只是我们手动实现的会更加灵活,可以自定义怎么返回,返回什么类型。但是呢语法糖又大大简化了我们实现的方式,提高了效率。

总结:

foreach本质
1.先获取in后面这个对象的 IEnumerator 会调用对象其中的GetEnumerator()方法来获取
2.执行得到这个IEnumerator的MoveNext()方法
3.如果MoveNext()返回true,就会得到Current
然后赋值给 item

相关推荐
Elastic 中国社区官方博客17 分钟前
我们如何通过 70 多个拉取请求,将 OpenTelemetry Java agent 迁移到 invokedynamic
java·大数据·开发语言·elasticsearch·搜索引擎·全文检索·es
tyn18830 分钟前
记录本地安装nacos服务的方法及异常
java·开发语言
小黄人软件33 分钟前
桌面需要整理吗?有工具自动分类
学习
步行cgn1 小时前
Spring MVC 视图机制的核心接口与类
java·开发语言·后端·spring·mvc
老衲不用@飘柔1 小时前
SFP与SFP+学习知识整理
学习
X54先生(人文科技)1 小时前
社会嵌入式终身学习机器与修复时代-ELR架构和技术演化探讨
人工智能·深度学习·学习·架构·开源协议
传奇开心果编程2 小时前
【Rust入门练中学】第5课:引用与借用
开发语言·rust·引用·编程语言理论·通用编程语言·借用
qetfw2 小时前
Bash 变量:export、位置参数与默认值展开验证
linux·开发语言·chrome·bash
电子云与长程纠缠2 小时前
UE5魂系游戏开发教程05
开发语言·学习·游戏·ui·ue5
syagain_zsx2 小时前
BRpc 二次封装:Channel 管理、服务管理与回调工厂
c++·学习·程序人生·rpc·谷歌