当前位置: 首页 > article >正文

【从零开始入门unity游戏开发之——C#篇41】C#迭代器(Iterator)——自定义类实现 foreach 操作

文章目录

  • 前言
  • 一、什么是迭代器?
  • 二、标准迭代器的实现方法
    • 1、自定义一个类`CustomList`
    • 2、让CustomList继承IEnumerable接口
    • 3、再继承IEnumerator接口
    • 4、完善迭代器功能
    • 5、**foreach遍历的本质**:
    • 6、在Reset方法里把光标复原
  • 三、用yield return语法糖实现迭代器
    • 1、用yield return语法糖为普通类实现迭代器
    • 2、用yield return语法糖为泛型类实现迭代器
  • 四、总结
  • 专栏推荐
  • 完结

前言

前面我们使用过foreach 来遍历过如列表数组等数据等,之所以可以这么做,其实就是它们内部已经帮我们实现了迭代器功能。

迭代器其实很像我们之前学过的自定义类排序——IComparable<T> 接口的实现,思路是类似的。

一、什么是迭代器?

在 C# 中,迭代器(Iterator)是一种特殊的方法,允许你在集合中按顺序逐个访问元素,而无需暴露集合的内部实现。通常,迭代器用于实现 foreach 循环。

C# 中有两种主要方式来实现迭代器:

  • IEnumerable<T>IEnumerator<T> 接口:这是实现迭代器的基础,通过实现这两个接口可以自定义迭代行为。
  • yield 语法糖:这是 C# 提供的一种简化的方式来实现迭代器。

语法糖是指某些语言特性或语法结构的简化,目的是提高代码的简洁性和可读性,同时不改变代码的语义或功能。

二、标准迭代器的实现方法

为了使自定义的类可以被foreach语句遍历,该类需要实现IEnumerable接口,并且通常还需要实现IEnumerator接口。IEnumerator接口提供了获取当前元素、移动到下一个元素以及重置位置的方法。通过实现这两个接口,我们能够控制如何遍历自定义类型的实例。

1、自定义一个类CustomList

class CustomList{
    private int[] list;
    public CustomList(){
        list = new int[] {1, 2, 3, 4, 5};
    }
}

现在直接使用foreach语句遍历CustomList肯定会报错,因为我们并没有实现迭代器
在这里插入图片描述

2、让CustomList继承IEnumerable接口

记得需要引入using System.Collections;命名空间,接口要求必须实现GetEnumerator方法

using System.Collections;

class CustomList : IEnumerable{
    private int[] list;
    
    public CustomList(){
        list = new int[] {1, 2, 3, 4, 5};
    }

    public IEnumerator GetEnumerator()
    {

    }
}

但是其实继承IEnumerable接口都不重要,只要实现了GetEnumerator方法即可
在这里插入图片描述

但是为什么要继承呢?其实就是规定你严格实现GetEnumerator方法,且这个方法也不好记

你会发现这时候,其实前面的foreach遍历就已经不报错了
在这里插入图片描述
现在执行遍历肯定还是走不通的,因为我们迭代器根本没实现任何内容

3、再继承IEnumerator接口

再继承IEnumerator接口,并实现里面的MoveNextReset方法和Current属性
在这里插入图片描述

4、完善迭代器功能

声明一个index 光标,完善迭代器功能

class CustomList : IEnumerable, IEnumerator{
    private int[] list;
    
    //从-1开始的光标用于表示数据得到了哪个位置
    private int index = -1;
    
    public CustomList(){
        list = new int[] {1, 2, 3, 4, 5};
    }

    public object Current => list[index];

    public IEnumerator GetEnumerator()
    {
        //直接把自己返回即可
        return this;
    }

    public bool MoveNext()
    {
        //移动光标
        index++;
        //是否溢出 溢出返回false
        return index < list.Length;
    }

    public void Reset()
    {
        throw new NotImplementedException();
    }
}

这时候前面foreach就可以打印出内容了

CustomList customList= new CustomList();
foreach (int item in customList){
    Console.WriteLine(item);
}

结果
在这里插入图片描述

5、foreach遍历的本质

  • 先获取in后面这个对象的IEnumerator,会调用对象其中的GetEnumerator方法来获取IEnumerator对象
  • 执行这个IEnumerator对象中的MoveNext方法
  • 只要MoveNext方法的返回值时true就会去得到Current的值然后赋值给item

6、在Reset方法里把光标复原

现在还差一个Reset方法没有实现,这又有什么用呢?

比如如果我们需要遍历两次数据

CustomList customList= new CustomList();

foreach (int item in customList){
    Console.WriteLine(item);
}

foreach (int item in customList){
    Console.WriteLine(item);
}

结果
在这里插入图片描述

结果只打印了一次数据,因为我们的光标一直在加,超出索引,再继续打印MoveNext一直返回false,就没有数据了,所以我们需要在Reset里把光标复原

public void Reset()
{
    //重置光标
    index = -1;
}

什么时候调用呢?在GetEnumerator方法里调用即可,每次foreach开始会得到一次IEnumerator,且只会运行一次,我们可以写个打印验证这一点

public IEnumerator GetEnumerator()
{
    Console.WriteLine("开始遍历");
    
    Reset();

    //直接把自己返回即可
    return this;
}

结果,遍历两次,打印了两次数据,且每次遍历开始都仅调用一次GetEnumerator方法获取IEnumerator
在这里插入图片描述
注:Reset方法重置光标位置,一般写在获取IEumerator对象这个函数中,用于每次foreach遍历开始时先重置光标位置

三、用yield return语法糖实现迭代器

前面实现这个迭代器是不是感觉非常麻烦?所以C#专门提供了yield return语法糖来帮助我们简化实现迭代器,yield return 会将当前值返回给调用者,并暂停执行,直到下次请求下一个值时继续。

1、用yield return语法糖为普通类实现迭代器

我们只需要继承IEnumerable接口,实现GetEnumerator方法即可

class CustomList : IEnumerable {
    private int[] list;
    
    public CustomList(){
        list = new int[] {1, 2, 3, 4, 5};
    }

    public IEnumerator GetEnumerator()
    {
        for (int i = 0; i< list.Length; i++){
            yield return list[i];
        }
    }
}

foreach 遍历打印

CustomList customList= new CustomList();

Console.WriteLine("第一次遍历");

foreach (int item in customList){
    Console.WriteLine(item);
}

Console.WriteLine("第二次遍历");

foreach (int item in customList){
    Console.WriteLine(item);
}

结果和前面一样,但是实现却方便了很多是不是
在这里插入图片描述
GetEnumerator里其实也可以这么写,效果一样

public IEnumerator GetEnumerator()
{
    // for (int i = 0; i< list.Length; i++){
    //     yield return list[i];
    // }
    
    yield return list[0];
    yield return list[1];
    yield return list[2];
    yield return list[3];
    yield return list[4];
}

但是通常肯定不会这么做,这里介绍这么写得方法,为了让你更容易理解yield return 的工作机制。

使用 yield return 的方法其实并没有创建一个完整的集合或数组,而是创建了一个延迟执行的状态机。每次调用迭代器方法时,都会从上一次暂停的位置继续执行yield return 使得方法的执行过程可以暂停和恢复,这就是懒加载的本质。

你可以会问,前面不是说了foreach 每次遍历开始都仅调用一次GetEnumerator方法获取IEnumerator吗?这和yield return机制好像冲突了。

其实不然。在 foreach 循环内部,GetEnumerator 方法只会在循环开始时被调用一次。每次迭代时,foreach 使用的是同一个 IEnumerator 对象,这个对象负责管理 yield return 的暂停和恢复。本质其实和前面标准迭代器的实现方法是一样的

2、用yield return语法糖为泛型类实现迭代器

泛型类实现其实也是一样,相信大家应该都懂了,这里就直接放出例子,大家参考参考

class CustomList<T> : IEnumerable 
{
    private T[] array;
    
    public CustomList(params T[] array){
        this.array = array;
    }

    public IEnumerator GetEnumerator()
    {
        for (int i = 0; i< array.Length; i++){
            yield return array[i];
        }
    }
}

调用

CustomList<string> customList= new CustomList<string>("向", "宇", "的", "客", "栈");

Console.WriteLine("第一次遍历");

foreach (string item in customList){
    Console.WriteLine(item);
}

Console.WriteLine("第二次遍历");

foreach (string item in customList){
    Console.WriteLine(item);
}

结果
在这里插入图片描述

四、总结

迭代器就是可以让我们在外部直接通过foreach遍历对象中元素而不需要了解其结构如何

主要的两种方式

  • 传统方式继承两个接口实现里面的方法
  • 用语法糖yield return去返回内容只需要继承一个接口即可

专栏推荐

地址
【从零开始入门unity游戏开发之——C#篇】
【从零开始入门unity游戏开发之——unity篇】
【制作100个Unity游戏】
【推荐100个unity插件】
【实现100个unity特效】
【unity框架开发】

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!如果你遇到任何问题,也欢迎你评论私信或者加群找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述


http://www.kler.cn/a/460254.html

相关文章:

  • 深入浅出:Spring Boot 自定义消息转换器的实现与应用
  • 选择器(结构伪类选择器,伪元素选择器),PxCook软件,盒子模型
  • 【C语言】可移植性陷阱与缺陷(三):整数的大小
  • html 音频和视频组件
  • 【人工智能机器学习基础篇】——深入详解深度学习之复杂网络结构:卷积神经网络(CNN)、循环神经网络(RNN)、生成对抗网络(GAN)等概念及原理
  • 常见硬件及其对应的驱动模块列表
  • 图像处理-Ch7-小波函数
  • 开源大数据平台E-MapReduce
  • 【广州计算机学会、广州互联网协会联合主办 | ACM独立出版 | 高录用】第四届大数据、信息与计算机网络国际学术会议(BDICN 2025)
  • 【电路理论四】正弦电流电路
  • 前端经典面试合集(二)——Vue/React/Node/工程化工具/计算机网络
  • Log4j2的Filters配置详解(ThresholdFilter )
  • ROS自学笔记三十:话题消息输出并转换为Excel形式
  • python钉钉机器人
  • 【探商宝】企业查询多维度解析---创新信息篇
  • [硬件] DELL BIOS 相关注意事项
  • 【漏洞复现】金和OA C6 FileDownLoad.aspx 任意文件读取漏洞复现
  • ImageSharp:高性能跨平台.NET开源图形库
  • Java垃圾回收机制与垃圾收集器
  • 期末速成C++【继承与派生 多态与虚函数】
  • TCP/IP 协议演进中的瓶颈,权衡和突破
  • VSCode快捷键Ctrl+/是注释;Ctrl+\是拆分编辑器;Ctrl+w是关闭编辑器
  • Jenkins 中的清理工作空间工作原理
  • 明达助力锻压设备工厂数字化改造
  • 0-指针网络(NIPS15)
  • 7-58 输出不重复的数组元素