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

news2024/11/23 8:38:30

本章主要介绍 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.coloradmin.cn/o/2198037.html

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

相关文章

谈论 MultiPHP

许多网站服务器提供商有大量客户&#xff0c;这些客户对他们的在线项目有各种需求。MultiPHP Manager界面可以帮助您轻松管理cPanel账户和域名的PHP和PHP-FPM配置&#xff0c;适用于运行EasyApache 4的系统。MultiPHP Manager界面提供了多种功能&#xff0c;包括&#xff1a; 服…

软考系统分析师知识点五:数据通信与计算机网络

前言 今年报考了11月份的软考高级&#xff1a;系统分析师。 考试时间为&#xff1a;11月9日。 倒计时&#xff1a;32天。 目标&#xff1a;优先应试&#xff0c;其次学习&#xff0c;再次实践。 复习计划第一阶段&#xff1a;扫平基础知识点&#xff0c;仅抽取有用信息&am…

安卓如何实现双击触摸唤醒点亮屏幕功能-源码分析linage os高通平台

背景&#xff1a; 前面文章已经有讲解过双击亮屏在一些方案调研情况&#xff0c;刚好linage os手机本身也有这个功能&#xff0c;刚好也有整体开源源码&#xff0c;所以今天带大家来对双击亮屏的源码部分进行剖析&#xff0c;本篇文章会一直分析到hal操作驱动节点。 设置作为…

有点晕,inline, crossinline,noinline小计

inline 主要用于展开铺平函数&#xff0c;用于高频访问但是代码不是很多的方法&#xff0c;减少函数对象的定义 fun <T> List<T>.normalForeach(action:(T)->Unit){for(item in this){action(item)} }inline fun <T> List<T>.inlinedForeach(action…

易图讯军用VR三维电子沙盘系统

深圳易图讯军用VR三维电子沙盘系统是一种集成了虚拟现实&#xff08;VR&#xff09;技术、三维建模技术、大数据分析、实时动态更新以及高度安全可靠的综合性军事指挥平台。该系统通过高精度三维模型真实再现战场环境&#xff0c;为指挥员提供沉浸式体验和交互操作的可能性&…

使用CANFD路由实现CAN与CANFD互通

随着科技的发展&#xff0c;汽车电子和工业领域中CAN通信需要承载数据量也越来越大&#xff0c;传统CAN通信有了向CANFD通信过渡的倾向。在实现过渡的过程中可能会出现自己设备是CAN通信&#xff0c;客户设备是CANFD通信的情况&#xff0c;或者自己设备是CANFD通信&#xff0c;…

react项目引入ant-design

当前react版本为&#xff1a; 1、安装antd npm install antd --save不需要安装babel-plugin-import&#xff0c;否则会报各种问题 2、引入Ant Design 样式 为了确保 Ant Design 样式在页面中生效&#xff0c;你需要在 _app.js 文件中全局引入样式。 当前项目用的Next.js。Ne…

vscode创建flutter项目,运行flutter项目

打开View&#xff08;查看&#xff09; > Command Palette...&#xff08;命令面板&#xff09;。 可以按下 Ctrl / Cmd Shift P 输入 flutter 选择Flutter: New Project 命令 按下 Enter 。选择Application 选择项目地址 输入项目名称 。按下 Enter 等待项目初始化完成 …

【HarmonyOS开发笔记 1】 -- 开发环境的搭建

DevEco Studio 的下载与安装 下载 下载路径&#xff1a; https://developer.huawei.com/consumer/cn/download/ 安装 解压后双击 deveco-studio-5.0.3.814.exe 指定安装目录&#xff0c;或者默认&#xff0c;然后下一步 一直“下一步”&#xff0c; 直到最后安装完成 新…

主机加固的关键要素:服务器防病毒

在数字化浪潮中&#xff0c;网络安全已成为企业不可忽视的一环。尤其是安全运维人员&#xff0c;他们肩负着保护企业数据不受侵害的重任。MCK主机加固解决方案&#xff0c;正是为了应对这一挑战而生。 网络安全的严峻现实 不久前&#xff0c;一家知名企业因勒索病毒攻击而被迫…

MVC、MVP和MVVM之间的区别

MVC&#xff08;Model-View-Controller&#xff09; 角色划分&#xff1a; Model&#xff1a;负责处理数据和业务逻辑&#xff0c;通常包括数据的存储、检索和更新等操作。 View&#xff1a;负责展示用户界面&#xff0c;接收用户输入&#xff0c;并将用户操作传递给 Controll…

如何在Visual Studio 2019中创建.Net Core WPF工程

如何在Visual Studio 2019中创建.Net Core WPF工程 打开Visual Studio 2019&#xff0c;选择Create a new project 选择WPF App(.Net Core) 输入项目名称和位置&#xff0c;单击Create 这样我们就创建好了一个WPF工程 工程文件说明 Dependencies 当前项目所使用的依赖库&…

java面向对之象类的继承与多态

目录 1.类的继承 图解 案例:创建一个动物类和一个猫类 1.代码 1)动物类 2)猫类 3.测试类 2.效果 2.父类方法的重写 案例:如何重写父类的方法 1.代码 1&#xff09;Animal类 2&#xff09;Dog类 3&#xff09;测试类 2.效果 3.super关键字 案例:如何在子类中调用父类的方…

如何使用 Puppeteer 和 Browserless 运行自动化测试?

Puppeteer&#xff1a;什么是 Puppeteer 及其功能 Puppeteer 是一个 Node.js 库。使用 Puppeteer&#xff0c;您可以在所有基于 Chromium 的浏览器上测试您的网站&#xff0c;包括 Chrome、Microsoft Edge Chrome 和 Chromium。此外&#xff0c;Puppeteer 可用于网页抓取、自动…

PDF处理技巧:Windows电脑如何选择合适的 PDF 编辑器

您可以阅读本文以了解用于在 PC 上编辑 PDF 的顶级免费软件&#xff0c;而无需花费任何费用即可轻松进行快速编辑、拆分、合并、注释、转换和共享您的 PDF。 PDF 或可移植文档文件是由 Adobe 创建的一种多功能文件格式。它可以帮助您轻松可靠地交换文档&#xff0c;无论相关方…

电脑端视频通过PCIE到FPGA端转UDP网络视频输出,基于XDMA+PHY芯片架构,提供3套工程源码和技术支持

目录 1、前言工程概述免责声明 2、相关方案推荐我已有的PCIE方案我这里已有的以太网方案 3、PCIE基础知识扫描4、工程详细设计方案工程设计原理框图电脑端视频PCIE视频采集QT上位机XDMA配置及使用XDMA中断模块FDMA图像缓存UDP视频组包发送UDP协议栈MAC数据缓冲FIFO组Tri Mode E…

基于php+uniapp微信小程序的电子书阅读系统snxr5

目录 项目介绍技术栈和环境说明具体实现截图php技术介绍文件解析微信开发者工具HBuilderXuniapp开发技术简介解决的思路性能/安全/负载方面数据访问方式PHP核心代码部分展示代码目录结构解析系统测试详细视频演示源码获取 项目介绍 &#xff08;1&#xff09;用户在安卓APP页面…

微信小程序启动不起来,报错凡是以~/包名/*.js路径的文件,都找不到,试过网上一切方法,最终居然这么解决的,【避坑】命运的齿轮开始转动

app.json "resolveAlias": {"~/*": "/*"},文件代码也没有问题&#xff0c;网上的方法试过来了&#xff0c;大模型AI也问过遍&#xff0c;熬夜到凌晨2点半&#xff0c;最不可思议的是居然是因为微信开发者工具版本的问题&#xff0c;我真的是笑死…

深入了解Oracle OCP认证,开启数据库专业之旅

使用Oracle数据库的公司内部&#xff0c;经常有员工们在讨论OCP认证(Oracle Certified Professional&#xff0c;Oracle认证专家)&#xff0c;这是甲骨文Oracle公司提供的一种专业认证&#xff0c;认证用于使用者在Oracle技术领域的专业知识和技能。 在这里&#xff0c;有一点…

Qt小bug — LINK : fatal error LNK1158: 无法运行“rc.exe“

Qt小bug —— LINK &#xff1a;fatal error LNK1158&#xff1a;无法运行"rc.exe" 环境 Qt 5.14.2 MSVC 2015 x64 现象 解决 在电脑上找到rc.exe 和rcdll.dll &#xff08;一般在C:\Program Files(x86)\Windows Kits*\bin\x64下面&#xff09;拷贝到 C:\Qt\Qt5…