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

news2025/3/9 22:49:26

文章目录

  • 前言
  • 一、什么是迭代器?
  • 二、标准迭代器的实现方法
    • 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.coloradmin.cn/o/2271173.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

win32汇编环境,对话框程序中通过资源显示bmp图像

;运行效果 ;win32汇编环境,对话框程序中通过资源显示bmp图像 ;通过资源的方式&#xff0c;会把图像固定在exe文件里&#xff0c;会变大。通过读取文件的方式&#xff0c;没有固定在exe文件里&#xff0c;也可以随时换图像文件&#xff0c;所以exe文件较小 ;直接抄进RadAsm可编译…

MATLAB画柱状图

一、代码 clear; clc; figure(position,[150,100,900,550])%确定图片的位置和大小&#xff0c;[x y width height] %准备数据 Y1[0.53,7.9,8.3;0.52,6.8,9.2;0.52,5.9,8.6;2.8,5.8,7.9;3.9,5.2,7.8;1.8,5.8,8.4]; % withoutNHC X11:6; %画出4组柱状图&#xff0c;宽度1 h1…

java 搭建一个springboot3.4.1项目 JDK21

环境准备 idea:2021 springboot:3.4.1 JDK:21 maven:3.6.3 新建项目 点击new->project->spring initializr 选择springboot版本 1.选择springboot版本&#xff0c;因为JDK版本是21因此对应springboot3.X Spring Boot 2.6.x&#xff1a;适用于JDK 8到17&#xff0c…

第R3周:RNN-心脏病预测

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 文章目录 一、前言二、代码流程1、导入包&#xff0c;设置GPU2、导入数据3、数据处理4、构建RNN模型5、编译模型6、模型训练7、模型评估 电脑环境&#xff1a;…

40% 降本:多点 DMALL x StarRocks 的湖仓升级实战

小编导读&#xff1a; 多点 DMALL 成立于2015年&#xff0c;持续深耕零售业&#xff0c;为企业提供一站式全渠道数字零售解决方案 DMALL OS。作为 DMALL OS 数字化能力的技术底座&#xff0c;大数据平台历经多次迭代平稳支撑了公司 To B 业务的快速开展。随着国家产业升级和云原…

Docker 环境中搭建 Redis 哨兵模式集群的步骤与问题解决

在 Docker 环境中搭建 Redis 哨兵模式集群的步骤与问题解决 在 Redis 高可用架构中&#xff0c;哨兵模式&#xff08;Sentinel&#xff09;是确保 Redis 集群在出现故障时自动切换主节点的一种机制。通过使用 Redis 哨兵&#xff0c;我们可以实现 Redis 集群的监控、故障检测和…

华为消费级QLC SSD来了

近日&#xff0c;有关消息显示&#xff0c;华为的消费级SSD产品线&#xff0c;eKitStor Xtreme 200E系列&#xff0c;在韩国一家在线零售商处首次公开销售&#xff0c;引起了业界的广泛关注。 尽管华为已经涉足服务器级别的SSD制造多年&#xff0c;但直到今年6月才正式推出面向…

StableDiffusionWebUI本地部署指南(WIN)

最近接手了一个需要使用 Stable Diffusion 的项目&#xff0c;要重新部署一套 SD 环境。这跟我之前的SD部署又不太一样&#xff0c;部署过程中遇到一些问题&#xff0c;总结出一个比较完美的安装方案&#xff0c;在这里和大家分享一下。 项目地址&#xff1a;https://github.c…

运动控制探针功能详细介绍(CODESYS+SV63N伺服)

汇川AM400PLC和禾川X3E伺服EtherCAT通信 汇川AM400PLC和禾川X3E伺服EtherCAT通信_汇川ethercat通信-CSDN博客文章浏览阅读1.2k次。本文详细介绍了如何使用汇川AM400PLC通过EtherCAT总线与禾川X3E伺服进行通信。包括XML硬件描述文件的下载与安装,EtherCAT总线的启用,从站添加…

ELK日志平台搭建 (最新版)

一、安装 JDK 1. 下载 JDK 21 RPM 包 wget https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.rpm2. 安装 JDK 21,使用 rpm 命令安装下载的 RPM 包&#xff1a; sudo rpm -ivh jdk-21_linux-x64_bin.rpm3. 配置环境变量 编辑 /etc/profile 文件以配置 JAVA_HO…

pygame飞机大战

飞机大战 1.main类2.配置类3.游戏主类4.游戏资源类5.资源下载6.游戏效果 1.main类 启动游戏。 from MainWindow import MainWindow if __name__ __main__:appMainWindow()app.run()2.配置类 该类主要存放游戏的各种设置参数。 #窗口尺寸 import random import pygame WIND…

应用架构模式

设计模式 设计模式是指根据通用需求来设计解决方案的模板或蓝图&#xff0c;使用设计模式能够更加有效地解决设计过程中的常见问题。设计模式针对不同的问题域有不同的内涵&#xff0c;主要涉及业务、架构、程序设计等问题域&#xff0c;本文主要讨论架构设计模式。 业务设计模…

el-input输入框需要支持多输入,最后传输给后台的字段值以逗号分割

需求&#xff1a;一个输入框字段需要支持多次输入&#xff0c;最后传输给后台的字段值以逗号分割 解决方案&#xff1a;结合了el-tag组件的动态编辑标签 那块的代码 //子组件 <template><div class"input-multiple-box" idinputMultipleBox><div>…

【新教程】华为昇腾NPU的pytorch环境搭建

1 硬件配置 使用学校的集群&#xff0c;相关配置如下&#xff1a; CPU&#xff1a;鲲鹏920 NPU&#xff1a;昇腾910B 操作系统&#xff1a;openEuler 22.03 2 安装版本 根据昇腾官方gitee上的信息&#xff0c;Pytoch 2.1.0是长期支持版本&#xff0c;因此选择安装这一版本&a…

游戏引擎学习第72天

无论如何&#xff0c;我们今天有一些调试工作要做&#xff0c;因为昨天做了一些修改&#xff0c;结果没有时间进行调试和处理。我们知道自己还有一些需要解决的问题&#xff0c;却没有及时完成&#xff0c;所以我们想继续进行这些调试。对我们来说&#xff0c;拖延调试工作总是…

信号的产生、处理

一、信号的概念 信号是linux系统提供的一种&#xff0c;向指定进程发送特定事件的方式。收到信号的进程&#xff0c;要对信号做识别和处理。信号的产生是异步的&#xff0c;进程在工作过程中随时可能收到信号。 信号的种类分为以下这么多种&#xff08;用指令kill -l查看&…

Node.js应用程序遇到了内存溢出的问题

vue 项目 跑起来&#xff0c;一直报错&#xff0c;内存溢出 在 文件node_modules 里 .bin > vue-cli-service.cmd 在依赖包这个文件第一行加上这个 node --max-old-space-size102400 "%~dp0\..\vue\cli-service\bin\vue-cli-service.js" %* node --max-old-s…

openGauss与GaussDB系统架构对比

openGauss与GaussDB系统架构对比 系统架构对比openGauss架构GaussDB架构 GaussDB集群管理组件 系统架构对比 openGauss架构 openGauss是集中式数据库系统&#xff0c;业务数据存储在单个物理节点上&#xff0c;数据访问任务被推送到服务节点执行&#xff0c;通过服务器的高并…

深入理解计算机系统—虚拟内存(一)

一个系统中的进程是与其他进程共享 CPU 和主存资源的。然而&#xff0c;共享主存会形成特殊的挑战。随着对 CPU 需求的增长&#xff0c;进程以某种合理的平滑方式慢了下来。但是如果太多的进程需要太多的内存&#xff0c;那么它们中的一些就根本无法运行。 为了更加有效地管理内…

九、Vue 事件处理器

文章目录 前言一、基础事件绑定:v-on 指令二、方法调用:组织有序的交互逻辑三、事件修饰符阻止冒泡与默认事件捕获与自身触发单次触发与鼠标按键区分四、按键修饰符前言 在 Vue.js 的交互世界里,事件处理器起着举足轻重的作用,它让页面从静态展示迈向动态交互,精准捕捉用户…