【从零开始入门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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/947408.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

WordPress新安装只安装主题后发现只有首页能打开,其他路由页面都是404,并且Elementor都打不开

找到wordpress安装路径的这个文件&#xff0c;有发现里面没有内容&#xff0c;添加下面内容保存&#xff0c;重启服务器即可 # BEGIN WordPress <IfModule mod_rewrite.c> RewriteEngine On RewriteBase / RewriteRule ^index\.php$ – [L] RewriteCond %{REQUEST_FILEN…

uniapp中使用ruoyiPlus中的加密使用(crypto-js)

package.json中添加 "crypto-js": "^4.2.0", "jsencrypt": "^3.3.2",但是vue2中使用 import CryptoJS from cryptojs; 这一步就会报错 参照 参照这里&#xff1a;vue2使用CryptoJS实现信息加解密 根目录下的js文档中新增一个AESwork.…

无需训练!多提示视频生成最新SOTA!港中文腾讯等发布DiTCtrl:基于MM-DiT架构

文章链接&#xff1a;https://arxiv.org/pdf/2412.18597 项目链接&#xff1a;https://github.com/TencentARC/DiTCtrl 亮点直击 DiTCtrl&#xff0c;这是一种基于MM-DiT架构的、首次无需调优的多提示视频生成方法。本文的方法结合了新颖的KV共享机制和隐混合策略&#xff0c;使…

RabbitMQ基础篇之快速入门

文章目录 一、目标需求二、RabbitMQ 控制台操作步骤1.创建队列2.交换机概述3.向交换机发送消息4.结果分析5.消息丢失原因 三、绑定交换机与队列四、测试消息发送五、消息查看六、结论 一、目标需求 新建队列&#xff1a;创建 hello.queue1 和 hello.queue2 两个队列。消息发送…

ESP32S3 + IDF 5.2.2 扫描WiFi

ESP32S3 IDF 5.2.2 扫描WiFi 目录 1 资料 2 通过Wi-Fi库扫描附近的网络 2.1 通过idf命令创建工程 2.2 编写测试用例 2.3 优化测试用例 3 小结 1 资料 在ESP平台基于IDF开发WiFi相关功能&#xff0c;主要就是基于IDF的Wi-Fi库进行二次开发。可供参考的官方资料&#xff…

2025-1-2-sklearn学习(30)模型选择与评估-验证曲线: 绘制分数以评估模型 真珠帘卷玉楼空,天淡银河垂地。

文章目录 sklearn学习(30) 模型选择与评估-验证曲线: 绘制分数以评估模型30.1. 验证曲线30.2. 学习曲线 sklearn学习(30) 模型选择与评估-验证曲线: 绘制分数以评估模型 文章参考网站&#xff1a; https://sklearn.apachecn.org/ 和 https://scikit-learn.org/stable/ 每种估…

统信系统设置代理的问题

统信系统设置代理的问题 问题表现方式一方式二 问题表现 统信系统下有系统代理和应用代理两个代理。设置系统代理时&#xff0c;git不能经过代理拉取代码。但是设置应用代理时&#xff0c;可以用git通过代理拉代码。 这是系统代理&#xff0c;在这里设置 ip 端口&#xff0c;…

STM32-笔记19-串口打印功能

复制项目文件夹03-流水灯&#xff0c;重命名为19-串口打印功能 打开项目 在主函数中&#xff0c;添加头文件、和串口初始化函数&#xff08;设置波特率&#xff09;和输出函数&#xff0c;如图所示&#xff1a; 软件部分就设置好了 下面是硬件部分 接线&#xff1a;使用USB…

JavaWeb——MySQL-DML(1/3)-添加数据insert(DML 操作概述、INSERT 语句插入数据、语句演示、总结)

目录 DML 操作概述 INSERT 语句插入数据 INSERT 语句基础语法 INSERT 语句演示 注意事项 总结 DML 操作概述 DML 简介 DML&#xff08;Data Manipulation Language&#xff09;即数据操作语言&#xff0c;用于对数据库表中的数据进行增删改操作&#xff0c;包括添加数据&…

Docker图形化界面工具Portainer最佳实践

前言 安装Portainer 实践-基于Portainer安装redis-sentinel部署 Spring Boot集成Redis Sentinel 前言 本篇文章笔者推荐一个笔者最常用的docker图形化管理工具——Portainer。 安装Portainer 编写docker-compose文件 Portainer部署的步骤比较简单&#xff0c;我们还是以…

Wonder Dynamics技术浅析(五):虚拟场景描述解析

虚拟场景描述解析模块是 Wonder Dynamics 平台的核心组件之一&#xff0c;其主要功能是将用户输入的自然语言场景描述转换为机器可理解的语义表示&#xff0c;为后续的虚拟场景生成提供基础数据。 一、文本预处理&#xff08;Text Preprocessing&#xff09; 1. 目标: 对用户…

基于SpringBoot在线竞拍平台系统功能实现十一

## 一、前言介绍&#xff1a;1.1 项目摘要 随着网络技术的飞速发展和电子商务的普及&#xff0c;竞拍系统作为一种新型的在线交易方式&#xff0c;已经逐渐深入到人们的日常生活中。传统的拍卖活动需要耗费大量的人力、物力和时间&#xff0c;从组织拍卖、宣传、报名、竞拍到成…

JavaScript基础 -- 变量、作用域与内存

1 原始值与引用值 原始值就是最简单的数据&#xff0c;引用值则是由多个值构成的对象。在把一个值赋给变量时&#xff0c;JavaScript引擎必须要确定这个值是原始值还是引用值 原始值大小固定&#xff0c;保存在栈内存上&#xff1b;引用值是对象&#xff0c;存储在堆内存上 它…

密钥登录服务器

1. 生成 SSH 密钥对 如果您还没有生成密钥对&#xff0c;可以使用以下命令生成&#xff1a; ssh-keygen 在 root 用户的家目录中生成了一个 .ssh 的隐藏目录&#xff0c;内含两个密钥文件&#xff1a;id_rsa 为私钥&#xff0c;id_rsa.pub 为公钥。 在提示时&#xff0c;您可…

nginx 部署前端vue项目

&#x1f468;‍⚕ 主页&#xff1a; gis分享者 &#x1f468;‍⚕ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕ 收录于专栏&#xff1a;前端工程师 文章目录 一、&#x1f353;什么是nginx&#xff1f;二、&#x1f353;nginx 部署…

三大行业案例:AI大模型+Agent实践全景

本文将从AI Agent和大模型的发展背景切入&#xff0c;结合51Talk、哈啰出行以及B站三个各具特色的行业案例&#xff0c;带你一窥事件驱动架构、RAG技术、人机协作流程&#xff0c;以及一整套行之有效的实操方法。具体包含内容有&#xff1a;51Talk如何让智能客服“主动进攻”&a…

HTML-CSS-常见标签与样式

目录 一. 央视新闻排版1.1 标题1.2 正文1.3 案例1.3.1 顶部导航栏1.3.2 flex布局1.3.3 表单标签1.3.4 表单项标签1.3.5 表格 1.3 课程总结 \quad 一. 央视新闻排版 \quad \quad 1.1 标题 \quad ALTp就是用AI快速生成 标题一共有6级 \quad 1.2 正文 \quad 定义视频 定义图片 样…

【信息安全设计】系统安全设计方案,系统安全保护设施设计实施方案(Word原件)

1.1 总体设计 1.1.1 设计原则 1.2 物理层安全 1.2.1 机房建设安全 1.2.2 电气安全特性 1.2.3 设备安全 1.2.4 介质安全措施 1.3 网络层安全 1.3.1 网络结构安全 1.3.2 划分子网络 1.3.3 异常流量管理 1.3.4 网络安全审计 1.3.5 网络访问控制 1.3.6 完整性检查 1.…

基于STP文件的智能比对系统:思通数科带来高效机械制造解决方案

在机械制造领域&#xff0c;设计图纸与实物之间的精准对比至关重要&#xff0c;传统的比对方式往往需要耗费大量时间且容易出现错误&#xff0c;导致生产效率低下并影响产品质量。为了解决这些问题推出了一套基于STP文件的智能比对系统&#xff0c;结合大模型技术&#xff0c;集…

【51项目】51单片机自制小霸王游戏机

视频演示效果&#xff1a; 纳新作品——小霸王游戏机 目录&#xff1a; 目录 视频演示效果&#xff1a; 目录&#xff1a; 前言&#xff1a; 一、连接方式&#xff1a; 1.1 控制引脚 1.2. 显示模块 1.3. 定时器 1.4. 游戏逻辑与硬件结合 1.5. 中断处理 二、源码分析&#xff1a…