图解C#高级教程(五):枚举器和迭代器

本章主要介绍 C# 当中枚举器、可枚举类型以及迭代器相关的知识。

文章目录

  • 1. 枚举器和可枚举类型
  • 2. IEnumerator 和 IEnumerable 接口
    • 2.1 IEnumerator 接口
    • 2.2 `IEnumerable` 接口
  • 3. 泛型枚举接口
  • 4. 迭代器
    • 4.1 使用迭代器创建枚举器
    • 4.2 使用迭代器创建可枚举类
    • 4.3 迭代器作为属性
    • 4.4 迭代器的使用场景
    • 4.4 迭代器的工作原理

1. 枚举器和可枚举类型

枚举器为我们提供了一种统一的方式来顺序访问集合中的元素,却不必了解集合内部的具体实现。例如,使用 foreach 语句遍历数组中的元素:

int[] arr = {10, 11, 12, 23};
foreach (int item in arr1)
{Console.WriteLine("Item value: {0}", item);
}

可枚举类型允许对象可以被枚举器逐一遍历,也就是说一个类型必须是可枚举的才能使用 foreach 循环进行遍历。

2. IEnumerator 和 IEnumerable 接口

2.1 IEnumerator 接口

一个枚举器必须实现 IEnumerator 接口的 3 个函数成员:

  • Current:返回序列中当前位置的属性。这个属性是只读的,返回的是 object 类型的引用,所以可以返回任何类型。
  • MoveNext 方法把枚举器位置移动到集合中的下一项。返回值为 bool 值,如果新的位置有效,返回 true;否则,返回 fasle。由于枚举器的原始位置在序列中的第一项之前,因此第一次使用 Current 之前必须先调用 MoveNext
  • Reset 把位置重置为原始状态。

设计的思想就是需要一个标志物(具体可以是引用、指针或者其它方式)能够获得被遍历集合的当前项,能够移动到集合中下一项的方法,以及重置当前位置的方法。

举一个例子,下图的左边是自定义的可枚举类型,右边是枚举器。
在这里插入图片描述

2.2 IEnumerable 接口

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

下面是一个结合使用枚举器和自定义可枚举类的例子:

using System;
using System.Collections;// 自定义整数范围类,实现 IEnumerable 接口
public class IntegerRange : IEnumerable
{private int _start;private int _end;public IntegerRange(int start, int end){_start = start;_end = end;}// 实现 IEnumerable 接口的 GetEnumerator 方法public IEnumerator GetEnumerator(){return new IntegerRangeEnumerator(_start, _end);}// 自定义枚举器类,实现 IEnumerator 接口private class IntegerRangeEnumerator : IEnumerator{private int _start;private int _end;private int _current;private bool _hasStarted;public IntegerRangeEnumerator(int start, int end){_start = start;_end = end;_hasStarted = false;}// 实现 MoveNext(),移动到下一个元素public bool MoveNext(){if (!_hasStarted){_current = _start;_hasStarted = true;}else{_current++;}return _current <= _end;}// 实现 Current 属性,返回当前元素public object Current{get{if (!_hasStarted || _current > _end)throw new InvalidOperationException();return _current;}}// 实现 Reset(),重置枚举器状态public void Reset(){_hasStarted = false;}}
}class Program
{static void Main(){IntegerRange range = new IntegerRange(1, 5);// 使用 foreach 遍历自定义的可枚举类型foreach (int number in range){Console.WriteLine(number);}}
}

输出:

1
2
3
4
5

3. 泛型枚举接口

在 C# 中,泛型枚举接口主要包括两个接口:IEnumerable<T>IEnumerator<T>。这两个接口分别用于实现泛型集合的枚举功能,允许集合中的元素在类型安全的情况下被逐个遍历。与非泛型版本相比,泛型接口提供了更好的类型安全性和性能,因为它避免了装箱/拆箱操作以及对 object 的转换。

对于泛型接口形式:

  • IEnumerable<T> 接口的 GetEnumerator 方法返回实现 IEnumerator 枚举器类的实例;
  • 实现 IEnumerator<T> 的类实现了 Current 属性,它返回实际类型的对象,而不是 object 基类的引用。

下面是一个实现 IEnumerable<T>IEnumerator<T> 的简单示例,定义了一个泛型范围类 Range<T>,可以生成从某个起始值到终止值的泛型集合。

using System;
using System.Collections;
using System.Collections.Generic;// 定义一个泛型范围类,实现 IEnumerable<T>
public class Range<T> : IEnumerable<T> where T : IComparable<T>
{private T _start;private T _end;private Func<T, T> _incrementFunc;public Range(T start, T end, Func<T, T> incrementFunc){_start = start;_end = end;_incrementFunc = incrementFunc;}// 实现 IEnumerable<T> 接口public IEnumerator<T> GetEnumerator(){return new RangeEnumerator(_start, _end, _incrementFunc);}// 显式实现非泛型的 IEnumerable 接口IEnumerator IEnumerable.GetEnumerator(){return GetEnumerator();}// 自定义泛型枚举器private class RangeEnumerator : IEnumerator<T>{private T _current;private T _start;private T _end;private Func<T, T> _incrementFunc;private bool _hasStarted;public RangeEnumerator(T start, T end, Func<T, T> incrementFunc){_start = start;_end = end;_incrementFunc = incrementFunc;_hasStarted = false;}// 实现 IEnumerator<T> 的 Current 属性public T Current{get{if (!_hasStarted)throw new InvalidOperationException();return _current;}}// 实现非泛型的 Current 属性object IEnumerator.Current => Current;// 实现 MoveNext(),移动到下一个元素public bool MoveNext(){if (!_hasStarted){_current = _start;_hasStarted = true;}else{_current = _incrementFunc(_current);}return _current.CompareTo(_end) <= 0;}// 实现 Reset(),重置枚举器public void Reset(){_hasStarted = false;}// 实现 Dispose() 方法public void Dispose() { }}
}class Program
{static void Main(){// 定义一个整数范围的泛型集合var range = new Range<int>(1, 5, x => x + 1);// 使用 foreach 遍历集合foreach (var number in range){Console.WriteLine(number);}}
}

Range<T>代码解释:

  1. Range<T> 是一个泛型类,允许生成从 _start_end 范围的集合。它实现了 IEnumerable<T> 接口,使该集合可以被 foreach 遍历。
  2. incrementFunc 是一个 Func<T, T> 类型的委托,指定如何生成下一个值。这使得 Range<T> 可以用于不同类型的泛型数据。

输出:

1
2
3
4
5

4. 迭代器

C# 从 2.0 版本开始提供了创建枚举器和可枚举类型的简单方式——迭代器。迭代器可为我们自动创建枚举器和可枚举类型。通过迭代器,程序可以按需逐步生成和返回集合中的元素,而无需一次性加载所有元素。

C# 的迭代器是通过使用两个关键字实现的:

  • yield return:用于按需返回集合中的下一个元素。
  • yield break:用于立即终止迭代。

当你实现一个迭代器方法时,C# 编译器会自动生成一个实现 IEnumerableIEnumerable<T> 接口的类,并生成适当的 IEnumeratorIEnumerator<T> 枚举器,因此不需要手动编写这些接口的实现代码。迭代器方法可以像常规方法一样被调用,并与 foreach 语句兼容。

4.1 使用迭代器创建枚举器

下面的实例展示了使用迭代器创建枚举器:

class MyClass
{public IEnumerator<string> GetEnumerator(){return BlackAndWhite();     // 返回枚举器}// 直接产生一个枚举器public IEnumerator<string> BlackAndWhite(){yield return "Black";yield return "White";yield return "gray";}class Program{static void Main(string[] args){MyClass myClass = new MyClass();foreach (string color in myClass){Console.WriteLine(color);}}}
}

输出如下:

Black
White
gray

编译器自动帮助我们做的工作如下图所示。编译器会为我们添加一个实现一个枚举器必须包含的方法。
在这里插入图片描述

4.2 使用迭代器创建可枚举类

下面的示例展示了使用迭代器创建可枚举类,这里的 BlackAndWhite 方法返回可枚举类型而不是枚举器,因此在 GetEnumrator 方法中需要调用可枚举类型对象的 GetEnumerator 方法来获取枚举器。

class MyClass1
{public IEnumerator<string> GetEnumerator(){IEnumerable<string> myEnum = BlackAndWhite();       // 获取可枚举类型return myEnum.GetEnumerator();          // 获取枚举器}public IEnumerable<string> BlackAndWhite(){yield return "Black";yield return "White";yield return "gray";}}class Program
{static void Main(string[] args){MyClass1 myClass1 = new MyClass1();// 使用类对象foreach (string color in myClass1){Console.WriteLine(color);}// 使用类枚举方法foreach (string color in myClass1.BlackAndWhite()){Console.WriteLine(color);}}
}

在这里插入图片描述

4.3 迭代器作为属性

下面的代码示例主要用来演示两个方面的内容:

  1. 使用迭代器产生具有两个枚举器的类;
  2. 演示迭代器作为属性而不是方法
    这段代码声明了两个属性来定义两个不同的枚举器。GetEnumerator 方法根据 _listFromUVtoIR 布尔变量的值返回两个枚举器中的一个。如果 _listFromUVtoIRtrue ,则返回 UVtoIR 枚举器;否则,返回 IRtoUV 枚举器。
class Spectrum
{bool _listFromUVtoIR;string[] colors = { "Red", "Green", "Blue", "Yellow", "Purple", "Orange" };public Spectrum(bool isFromUVtoIR){_listFromUVtoIR = isFromUVtoIR;}public IEnumerator<string> GetEnumerator(){return _listFromUVtoIR? UVtoIR: IRtoUV;}public IEnumerator<string> UVtoIR{get{for (int i = 0; i < colors.Length; i++){yield return colors[i];}}}public IEnumerator<string> IRtoUV{get {for (nint i = colors.Length - 1; i >= 0; i--)yield return colors[i];}}
}class Program
{static void Main(){Spectrum startUV = new Spectrum(true);Spectrum startIR = new Spectrum(false);foreach (string color in startUV){Console.Write("{0} ", color);}Console.WriteLine();foreach (string color in startIR){Console.Write("{0} ", color);}}
}

输出:

Red Green Blue Yellow Purple Orange
Orange Purple Yellow Blue Green Red

下面代码的说明。

public IEnumerator<string> IRtoUV
{get {for (nint i = colors.Length - 1; i >= 0; i--)yield return colors[i];}
}

IRtoUV 是一个只读属性,它的类型是 IEnumerator<string>。当访问该属性时,get 访问器中的代码会被执行,返回一个 IEnumerator<string> 对象。这个 get 访问器使用了 yield return,因此它定义了一个迭代器,用于逐步返回 colors 数组中的元素,按从后往前的顺序。

4.4 迭代器的使用场景

迭代器的使用场景如下:

  1. 延迟执行
    迭代器提供了按需生成元素的能力,这意味着元素只有在被请求时才会生成。这对于处理大数据集合、流数据或计算开销较大的数据非常有用。举个例子,如果你有一个需要大量计算才能得到的值,使用迭代器可以避免一次性计算所有值,而是每次只计算一个值。
static IEnumerable<int> GenerateLargeNumbers()
{for (int i = 0; i < int.MaxValue; i++){yield return i;}
}
  1. 自定义集合的遍历
    当你创建自定义的数据结构时,可以使用迭代器来定义集合的遍历规则,而不需要手动实现 IEnumerableIEnumerator 接口。例如,使用迭代器遍历二叉树:
using System;
using System.Collections.Generic;// 定义二叉树节点类
public class TreeNode
{public int Value;  // 节点的值public TreeNode Left;  // 左子节点public TreeNode Right;  // 右子节点public TreeNode(int value){Value = value;}// 使用迭代器实现深度优先遍历(前序遍历:根 -> 左 -> 右)public IEnumerable<int> PreOrderTraversal(){// 返回当前节点的值yield return Value;// 如果左子节点存在,递归遍历左子树if (Left != null){foreach (var leftValue in Left.DepthFirstTraversal()){yield return leftValue;}}// 如果右子节点存在,递归遍历右子树if (Right != null){foreach (var rightValue in Right.DepthFirstTraversal()){yield return rightValue;}}}// 中序遍历:左 -> 根 -> 右)public IEnumerable<int> InOrderTraversal(){if (Left != null){foreach (var leftValue in Left.InOrderTraversal()){yield return leftValue;}}yield return Value;if (Right != null){foreach (var rightValue in Right.InOrderTraversal()){yield return rightValue;}}}// 后序遍历:右 -> 根 -> 左public IEnumerable<int> PostOrderTraversal()
{if (Left != null){foreach (var leftValue in Left.PostOrderTraversal()){yield return leftValue;}}if (Right != null){foreach (var rightValue in Right.PostOrderTraversal()){yield return rightValue;}}yield return Value;
}}class Program
{static void Main(){// 构建一棵二叉树TreeNode root = new TreeNode(1);root.Left = new TreeNode(2);root.Right = new TreeNode(3);root.Left.Left = new TreeNode(4);root.Left.Right = new TreeNode(5);root.Right.Left = new TreeNode(6);root.Right.Right = new TreeNode(7);// 使用深度优先遍历二叉树Console.WriteLine("DFS Traversal (Pre-order):");foreach (int value in root.PreOrderTraversal()){Console.WriteLine(value);}}
}

输出:

DFS Traversal (Pre-order):
1 2 4 5 3 6 7
DFS Traversal (In-order):
4 2 5 1 6 3 7
DFS Traversal (Post-order):
4 5 2 6 7 3 1

二叉树的形状:
在这里插入图片描述

4.4 迭代器的工作原理

在后台,由编译器生成的枚举器类是包含4个状态的状态机。

  • Before 首次调用 MoveNext 的初始状态。
  • Running 调用MoveNext后进入这个状态。在这个状态中,枚举器检测并设置下一项的位置。在遇到yield returnyield break 或在迭代器体结束时,退出状态。
  • Suspended 状态机等待下次调用 MoveNext 的状态。
  • After 没有更多项可以枚举。
    在这里插入图片描述

好了,以上就是对 C# 当中枚举器、可枚举类以及迭代器的介绍。如有收获,记得一键三连呐。

最后,给各位道友介绍一下使用国外虚拟卡开通一些国外服务的渠道,当前我在用的是 wildcard,使用我的注册邀请码 IOQ1YDHH 注册,你能得 2 美刀呢。能抵消一部分的开卡费用。自己买个 Github CopilotChatGPT Plus (不用注册账号即可使用)简直不要太香。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

消峰限流有哪几种方式?

消峰限流的方式 业务视角 验证码回答问题环节 技术视角 消息队列异步化用户请求 限流&#xff0c;对流量进行层层过滤 nginx 层限流&#xff0c; 一是控制速率 limit_req 漏桶算法 limit_req_zone $binary_remote_addr zonemylimit:10m rate2r/s; server { location / { lim…

leetcode链表(三)-反转链表

题目 . - 力扣&#xff08;LeetCode&#xff09; 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 思路 首先定义一个cur指针&#xff0c;指向头结点&#xff0c;再定义一个pre指针&#xff0c;初始化为None。 然后就要开始反转了&…

其他浏览器可以联网,但edge不能联网

问题描述&#xff1a; 今早edge无法上网&#xff0c;检测网络连接正常&#xff0c;而且其他chrome&#xff0c;Firefox和360浏览器都可以上网。 解决方案&#xff1a; 注意&#xff1a;为防止是代理问题&#xff0c;可以在扩展中禁用后再试试 如果没有代理或者禁用代理也不…

基于SpringBoot摄影师分享交流社区【附源码】

基于SpringBoot摄影师分享交流社区 效果如下&#xff1a; 系统首页界面 用户注册界面 作品信息页面 公告资讯页面 管理员登录页面 管理员功能界面 作品类别界面 作品信息界面 研究背景 随着互联网技术的快速发展&#xff0c;数字摄影技术的普及使得越来越多的摄影爱好者渴望…

Python、R语言Lasso、Ridge岭回归、XGBoost分析Airbnb房屋数据:旅游市场差异、价格预测|数据分享...

全文链接&#xff1a;https://tecdat.cn/?p37839 分析师&#xff1a;Kefan Yu 在大众旅游蓬勃发展的背景下&#xff0c;乡村旅游已成为推动乡村经济、社会和文化发展的关键力量。当前&#xff0c;乡村旅游接待设施主要以招待所、小宾馆和农家乐等形式存在。然而&#xff0c;一…

企业远控私有化部署解决方案-内信互联

内信互联&#xff08;DoLink&#xff09;&#xff0c;是点量软件新推出的企业私有化远程控制系统解决方案。很多朋友对这个产品还不是很了解&#xff0c;今天点量小编就对其基础功能做一些详细说明&#xff0c;如果您想快速拥有自己的企业私有远程控制系统&#xff0c;欢迎联系…

基于SpringBoot+Vue+MySQL的企业招聘管理系统

系统展示 用户前台界面 管理员后台界面 企业后台界面 系统背景 在当今数字化转型的大潮中&#xff0c;企业对于高效、智能化的人力资源管理系统的需求日益增长。招聘作为人力资源管理的首要环节&#xff0c;其效率与效果直接影响到企业的人才储备与竞争力。传统的招聘方式不仅耗…

摩托车一键启动兼容机械钥匙点火

摩托车无钥匙一键启动 工作原理 摩托车无钥匙一键启动系统的工作原理主要依赖于RFID无线射频技术和车辆身份编码识别系统。这种技术能够通过小型化、小功率的射频天线来识别车辆的身份&#xff0c;并与遥控系统结合&#xff0c;实现双重射频系统和双重防盗保护。这意味着&…

《深度学习》【项目】OpenCV 答题卡识别 项目流程详解

目录 一、项目上半部分 1、定义展示图像函数 2、预处理 运行结果&#xff1a; 3、轮廓检测并绘制 运行结果&#xff1a; 4、排序轮廓 5、定义排序点函数 6、透视变换 1&#xff09;定义透视变换处理函数 2&#xff09;执行透视变换 运行结果&#xff1a; 7、二值化…

信息安全工程师(28)机房安全分析与防护

前言 机房安全分析与防护是一个复杂而细致的过程&#xff0c;涉及到物理安全、环境控制、电力供应、数据安全、设备管理、人员管理以及紧急预案等多个方面。 一、机房安全分析 1. 物理安全威胁 非法入侵&#xff1a;未经授权的人员可能通过门窗、通风口等进入机房&#xff0c;…

解决无法安装“vue.volar“扩展,跟vscode版本不兼容问题

问题&#xff1a;安装volar插件的时候提示跟vscode版本不兼容 解决方案 1、进入VSCode插件市场&#xff0c;搜索Vue.volar&#xff08;直达链接&#xff1a;volar下载界面&#xff09; 2、点击download Extension&#xff08;下载插件&#xff09; 3、下载.vsix文件完成后&a…

基于Opencv中的DNN模块实现图像/视频的风格迁移

一、DNN模块的介绍 1、简介 OpenCV中的DNN&#xff08;Deep Neural Network&#xff09;模块是一个功能强大的组件&#xff0c;它支持深度学习网络模型的加载和推理。虽然DNN模块不提供模型的训练功能&#xff0c;但它可以与主流的深度学习框架&#xff08;如TensorFlow、Caf…

Rope – 基于深度学习模型开源的AI换脸技术

Rope是什么 Rope是一款开源的AI换脸工具&#xff0c;基于insightface的inswapper_128模型构建&#xff0c;提供一个用户友好的图形界面。用户通过上传图片或视频&#xff0c;在几秒钟内完成换脸操作&#xff0c;效果逼真。Rope支持多种超分辨率算法&#xff0c;支持用户调整面…

深入探讨B+树索引的基本概念、工作原理以及在MySQL中的应用

文章目录 1. B树的基本概念2. B树在MySQL中的实现3. 示例代码4. 结论 在数据库管理系统中&#xff0c;索引是一种特殊的文件&#xff0c;它能够提高数据检索的速度。MySQL作为最流行的开源关系型数据库之一&#xff0c;提供了多种索引类型来满足不同的性能需求。其中&#xff0…

ARP断网攻击

ARP断网攻击 1.课前准备 kali 作为ARP攻击机&#xff0c;192.168.110.26 MAC地址&#xff1a;00:0c:29:fc:66:46 win10 作为被攻击方&#xff0c;192.168.110.12 MAC地址&#xff1a;1c:69:7a:a4:cf:92 网关&#xff08;路由器&#xff09;&#xff0c;192.168.110.1 MAC地…

[单master节点k8s部署]34.ingress 反向代理(一)

ingress是k8s中的标准API资源&#xff0c;作用是定义外部流量如何进入集群&#xff0c;并根据核心路由规则将流量转发到集群内的服务。 ingress和Istio工作栈中的virtual service都是基于service之上&#xff0c;更细致准确的一种流量规则。每一个pod对应的service是四层代理&…

ESP32接入扣子(Coze) API使用自定义智能体

使用ESP32接入Coze API实现聊天机器人的教程 本示例将使用ESP32开发板通过WiFi接入 Coze API&#xff0c;实现一个简单的聊天机器人功能。用户可以通过串口向机器人输入问题&#xff0c;ESP32将通过Coze API与智能体进行通信&#xff0c;并返回对应的回复。本文将详细介绍了如…

PyCharm打开及配置现有工程(详细图解)

本文详细介绍了如何利用Pycharm打开一个现有的工程&#xff0c;其中包括编译器的配置。 PyCharm打开及配置现有工程 1、打开工程2、配置编译器 1、打开工程 双击PyCharm软件&#xff0c;点击左上角 文件 >> 打开(O)… 选中想要打开的项目之后点击“确定” 2、配置编译器…

[Algorithm][贪心][可被三整除的最大和][距离相等的条形码][重构字符串]详细讲解

目录 1.可被三整除的最大和1.题目链接2.算法原理详解3.代码实现 2.距离相等的条形码1.题目链接2.算法原理详解3.代码实现 3.重构字符串1.题目链接2.算法原理详解3.代码实现 1.可被三整除的最大和 1.题目链接 可被三整除的最大和 2.算法原理详解 思路&#xff1a;正难则反 贪…

326. 3 的幂

文章目录 326. 3 的幂解题思路Go代码 326. 3 的幂 326. 3 的幂 给定一个整数&#xff0c;写一个函数来判断它是否是 3 的幂次方。如果是&#xff0c;返回true&#xff1b;否则&#xff0c;返回 false 。 整数 n 是 3 的幂次方需满足&#xff1a;存在整数 x 使得 n 3 x n …