(九)枚举器和迭代器(2)

news2024/11/27 22:38:28

一、Enumerator 接口

实现了 IEnumerator 接口的枚举器包含3个函数成员:Current、MoveNext 以及 Reset。

1)Current: 返回序列中当前位置项的属性。

  • 只读属性。
  • 返回 Object 类型。可以返回对应的可枚举类型。

2)MoveNext: 枚举器位置移到集合中下一个元素的方法,返回值为 bool,返回值代表位置的状态,是有效位置还是到末尾项的位置。

  • 返回值为 true,说明新的位置是有效的。
  • 返回值为 false,说明新的位置无效,也有可能到了末尾的位置。
  • 枚举器的原始位置在序列中的第一项之前,因此 MoveNext 必须在第一次使用 Current 之前调用。

3)Reset: 把位置重置为原始状态的方法。

请添加图片描述

二、IEnumberable 接口

可枚举类型之所以能通过 GetEnumerator 方法来获取枚举器对象,是因为 IEnumberable 接口里有这个方法,并且可枚举类型实现了 IEnumberable 接口的类。

class MyClass:IEnumerable
{
public IEnumberator GetEnumerator{...}
}

请添加图片描述

使用 IEnumerbalbe 和 IEnumerator 的示例

    //枚举器
    class ColorEnumberator : IEnumerator
    {
        string[] colors;
        int position = -1;

       public ColorEnumberator(string[] theColors)
        {
            colors = new string[theColors.Length];
            for (int i = 0; i < theColors.Length; i++)
                colors[i] = theColors[i];
        }

        public object Current
        {
            get
            {
                if(position == -1)
                throw new InvalidOperationException();
                if(position >= colors.Length)
                    throw new InvalidOperationException();

                return colors[position];
            }
        }

        public bool MoveNext()
        {
            if(position < colors.Length - 1)
            {
                position++;
                return true;
            }
            else
            {
                return false;
            }
        }

        public void Reset()
        {
            position = -1;
        }
    }

    //可枚举类型
    class  Spectrum:IEnumerable
    {
        string[] Colors = { "violet", "blue", "cyan", "green", "yellow", "orange", "red" };

        public IEnumerator GetEnumerator()
        {
            //创建自定义枚举器ColorEnumberator 对象
            return new ColorEnumberator(Colors);//此处断点调试
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Spectrum spectrum = new Spectrum();
            foreach (string color in spectrum)//此处断点调试,可进入到GetEnumerator() 方法里
                Console.WriteLine(color);
            Console.ReadKey();
        }
    }

IEnumerable: 用来实现可枚举类型对象,并调用 GetEnumerator 方法获取枚举器
IEnumerator: 用来自定一个枚举器类型

三、泛型枚举接口

泛型枚举接口:

IEnumerable<T>
IEnumerator<T>

对于非泛型接口:

  • IEnumerable 接口的 GetEnumerator 方法返回实现 IEnumerator 的枚举器实例;
  • 实现 IEnumerator 的类实现了 Current 属性,它返回 object 类型的引用,然后我们必须把它转化为对象的实际类型。

泛型接口继承自非泛型接口。对于泛型接口形式:

  • IEnumerable <T> 接口的GetEnumerator 方法返回实现 IEnumertor <T> 接口的枚举器的实例。
  • 实现 IEnumertor <T> 的类实现了 Current 属性,它返回实际类型的实例,而不是object 基类的引用。
  • 这些是协变接口,所以它们实际声明为 IEnumerable < out T> 、 IEnumertor < out T>

类型安全
==非泛型接口的实现不是类型安全的。==它们返回 object 类型的引用,然后必须转化为实际类型。
泛型接口的枚举器是类型安全的,它返回实际类型的引用。
图19-7实现 IEnumerator 接口的类的结构

请添加图片描述

图19-7实现 IEnumerable 接口的类的结构

请添加图片描述

四、迭代器

编译器为我们创建了枚举器和可枚举类型,这种结构叫作迭代器

1、声明迭代器

以下是声明两个迭代器的版本

版本1

//声明迭代器 BlackAndWhite;
public IEumerator<string> BlackAndWhite()
{
yield return "black";//如果没有返回,则执行下一条返回语句
yield return "gray";
yield return "white";
}

版本2

//声明迭代器 BlackAndWhite;
public IEumerator<string> BlackAndWhite()
{
string[] theColors = { "black","gray","white" };
for(int i = 0;i < theColors.Length;i++)
yield return theColors[i];
//如果没有返回则再遍历一次;如果能返回,则结束后续迭代
}

yield return 在枚举器中的作用: 每遍历一次访问 Current 属性时,就要返回一个新值,而不是一次返回所有元素。

2、迭代器块

迭代器块是有一个或多个 yield 语句的代码块。

迭代器块有3种:

  • 方法主体;
  • 访问器主体;
  • 运算器主体。

迭代器块与其他代码块不同。其他块包含的语句被当作是命令式的。也就是说,先执行代码块的第一个语句,然后执行后面的语句,最后控制离开块。

另一方面,迭代器不是需要再同一时间执行的一串命令式命令,而是声明性的,它描述了希望编译器为我们创建的枚举器类的行为。迭代器块中的代码描述了如何枚举元素。

迭代器块有两个特殊语句。

  • yield return 语句指定了序列中要返回的下一项。
  • yield break 语句指定再序列中没有其他项。

可以让迭代器产生枚举器或可枚举类型

//产生枚举器的迭代器
public IEnumerator<string> IteratorMethod()
{
yield retrun...
}

//产生可枚举类型的迭代器
public IEnumerable<string> IteratorMethod()
{
yield retrun...
}

使用迭代器来创建枚举器

 class  MyClass
    {
        public int  CurrentIndex = 0;
        //由于实现了GetEnumerator方法,MyClass 类是可枚举类型
        public IEnumerator<string> GetEnumerator()
        {
            return BlackAndWhite();//返回枚举器
        }

        public IEnumerator<string> BlackAndWhite()//迭代器
        {
            yield return "black ";
            yield return "gray ";
            yield return "white " ;
            //返回枚举器 IEnumerator<string>
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MyClass mc = new MyClass();
            foreach (string shade in mc)
                Console.WriteLine(shade);

            Console.ReadKey();
        }
    }

输出结果:

black
gray
white

问题: BlackAndWhite 的作用是通过迭代器来创建一个枚举器,可 foreach 语句为什么会遍历输出 BlackAndWhite 方法里的所有返回值呢?

要想回答这个问题,以下我就通过把 BlackAndWhite 方法调整为:
(在 MyClass 类添加了一个 变量 CurrentIndex,以追踪枚举器获取当前值的位置)

  public IEnumerator<string> BlackAndWhite()//迭代器
        {
        //第一次遍历,返回含有black内容的枚举器
            yield return "black " + CurrentIndex;
            CurrentIndex++;
            
            //第二次遍历,返回含有gray内容的枚举器
            yield return "gray " + CurrentIndex;
            CurrentIndex++;
            
          //第三次遍历,返回含有white内容的枚举器
            yield return "white " + CurrentIndex;

            //该注释掉的代码块,其输出结果跟以上不含有CurrentIndex的代码相同
            //迭代器里含有字符串数组,是可枚举类型中的内容
            //string[] theColors = { "black", "gray", "white" };
            //for (int i = 0; i < theColors.Length; i++)
            //    yield return theColors[i];
        }

输出结果:

black 0
gray 1
white 2

由以上的代码可知,foreach 每次遍历访问元素,首先都要调用一次 BlackAndWhite 方法,然后再返回其枚举器,执行 当前 yield return 之后,不会继续执行下行代码,直到下一次遍历的时候,才会继续下一条 yield return 语句。

请添加图片描述

使用迭代器来创建可枚举类型

  class  MyClass
    {
        public IEnumerator<string> GetEnumerator()
        {
            IEnumerable<string> mEnumerable = BlackAndWhite();//获取可枚举类型
            return mEnumerable.GetEnumerator();//获取枚举器
        }

        //IEnumerable<string>:返回可枚举类型
        public IEnumerable<string> BlackAndWhite()
        {
            yield return "black";
            yield return "gray";
            yield return "white";

        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            MyClass mc = new MyClass();

            foreach (string shade in mc)//使用类对象
                Console.Write($"{ shade }  ");

            foreach (string shade in mc.BlackAndWhite())//使用枚举器方法
                Console.Write($"{ shade }  ");

            Console.ReadKey();
        }
    }

输出结果:

black gray white black gray white

请添加图片描述

3、总结常见迭代器模式

枚举器的迭代器模式

class MyClass
{
public IEnumerator<string> GetEnumerator()
{
return IteratorMethod();
}

public IEnumerator<string> IteratorMethod()
{
...
yield return ...;
}
}

void Main()
{
MyClass mc = new MyClass();
foreach(string x in mc)
...
}

可枚举类型的迭代器模式

class MyClass
{
public Ienumerator<string> GetEnumerator()
{
return IteratorMethod().GetEnumerator();
}

public IEnumerable<string> IteratorMethod()
{
yield return...
}

void Main()
{
MyClass mc = new MyClass();
foreach(var string x in mc)
...

foreach(string x in mc.IteratorMethod())
...

}
}

4、产生多个可枚举类型

使用迭代器来产生具有两个可枚举类型的类。

Specturm 类有两个可枚举类型的迭代器 —— 一个从紫外线到红外线枚举光谱中的颜色,而另一个以逆序进行枚举。Spectrum 类不是可枚举类型,但是它有两个方法返回可枚举类型。

    class Spectrum
    {
        string[] colors = { "violet", "blue", "cyan", "green", "yellow", "orange", "red" };
         //返回可枚举类型
        public IEnumerable<string> UVtoIR()
        {
            for (int i = 0; i < colors.Length; i++)
                yield return colors[i];
        }
         //返回可枚举类型
        public IEnumerable<string> IRtoUV()
        {
            for (int i = colors.Length - 1; i >= 0; i--)
                yield return colors[i];
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Spectrum spectrum = new Spectrum();

            foreach (string color in spectrum.UVtoIR())
                Console.Write($"{ color }   ");
            Console.WriteLine();

            foreach (string color in spectrum.IRtoUV())
                Console.Write($"{ color }   ");
            Console.WriteLine();

            Console.ReadKey();
        }
    }

输出结果:

violet blue cyan green yellow orange red
red orange yellow green cyan blue violet

5、将迭代器作为属性

本例演示两个方面的内容:

  • 1)使用迭代器来产生具有两个枚举器的累;
  • 2)演示迭代器如何实现为属性而不是方法。
   class Spectrum
    {
        bool _listFromUVtoIR;
        string[] colors = { "violet", "blue", "cyan", "green", "yellow", "orange", "red" };

        public Spectrum(bool lisatFromUVtoIR)
        {
            _listFromUVtoIR = lisatFromUVtoIR;
        }

        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 (int i = colors.Length - 1; i >= 0; i--)
                    yield return colors[i];
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Spectrum startUV = new Spectrum(true);
            Spectrum startIR = new Spectrum(false);

            foreach (string color in startUV)
                Console.Write($"{ color }   ");
            Console.WriteLine();

            foreach (string color in startIR)
                Console.Write($"{ color }   ");
            Console.WriteLine();

            Console.ReadKey();
        }
    }

输出结果:

violet blue cyan green yellow orange red
red orange yellow green cyan blue violet

6、迭代器的实质

迭代器的其他重要事项

  • 迭代器属于 System.Collection.Generic 命名空间里的需要使用 using 指令引入它。
  • 在编译器生成的枚举器中,不支持 Reset 方法。它是接口需要的方法,所以实现了它,但调用时总是抛出 System.NotSupportedException 异常。

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

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

如果状态机在 Before 或 Suspended 状态时调用了 MoveNext 方法,就转到了 Running 状态。在Running 状态中,它检测集合的下一项并设置位置。

如果有更多项,状态机会转入 Suspended 状态;如果没有更多项,它转入并保持在 After 状态。

请添加图片描述

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

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

相关文章

牛客网基础语法41~50题

牛客网基础语法41~50题&#x1f618;&#x1f618;&#x1f618; &#x1f4ab;前言&#xff1a;今天是咱们第五期刷牛客网上的题目。 &#x1f4ab;目标&#xff1a;熟练用数学知识来解决编程问题&#xff0c;会利用每种循环。 &#x1f4ab;鸡汤&#xff1a;压抑了&#xff0…

8 个身份自动化模版,帮你提升企业身份治理效率

中大型企业的信息化建设已经成为了当前企业的主流趋势。随着日益增长的业务需求&#xff0c;中大型企业的信息系统逐渐发展成为一个拥有众多应用和数据库支撑的复杂体系。由于中大型企业员工众多&#xff0c;部门繁杂&#xff0c;且存在大量员工入、转、调、离等情况&#xff0…

springboot集成quartz

文章目录 前言一、Quartz是什么&#xff1f;难易程度 二、核心思想三、使用步骤1.引入依赖2.创建相应类 总结 前言 引用上一篇博客的说法 springboot整合xxl-job ,集群模式下,定时任务会造成很严重的事故,其次普通的任务也无法做到像xxl-job与Quartz 一样,及时启停,修改等;上一…

文心一言 VS 讯飞星火 VS chatgpt (41)-- 算法导论5.4 5题

五、在大小为n的集合中&#xff0c;一个k字符串构成一个k排列的概率是多少?这个问题和生日悖论有什么关系? 文心一言&#xff1a; 在大小为n的集合中&#xff0c;一个k字符串构成一个k排列的概率是1/k!&#xff0c;这是由于排列的总数是k!&#xff0c;而每个字符串被选中的…

十大开源 ChatGPT 替代品:弥合对话式 AI 的差距

聊天机器人彻底改变了企业与客户互动的方式。由 OpenAI 开发的最先进的 GPT-4 是该领域的主导者。但是&#xff0c;它不是开源的&#xff0c;这限制了开发人员复制结果或开发自己的类似于 GPT-4 的聊天机器人。 为了填补这一空白&#xff0c;开源社区已经开始提供 GPT-4 的替代…

Qt6.5.1+WebRTC学习笔记(十二)环境搭建流媒体服务器(ubuntu22.04+SRS)

前言 若只是实现一对一通信&#xff0c;仅使用webrtc就足够了。但有时间需要进行多个人的直播会议&#xff0c;当人比较多时&#xff0c;建议使用一个流媒体服务器&#xff0c;笔者使用的是SRS。 这个开源项目资料比较全&#xff0c;笔者仅在此记录下搭建过程 一、准备 1.操…

移远通信率先完成多场5G NTN技术外场验证,为卫星物联网应用落地提速

近日&#xff0c;由中国电信卫星公司牵头&#xff0c;移远通信联合紫光展锐、鹏鹄物宇等行业上下游合作伙伴&#xff0c;针对现有蜂窝通信在信号覆盖盲区&#xff0c;信息监测数据无法实时回传等痛点问题&#xff0c;以领先行业的速度开展了一系列的5G NTN&#xff08;non-terr…

Unity入门7——物理系统之碰撞检测

一、刚体 Rigid Body ​ 刚体利用体积&#xff08;碰撞器 Collider&#xff09;进行碰撞计算&#xff0c;模拟真实的碰撞效果&#xff0c;产生力的作用 ​ 碰撞产生的必要条件&#xff1a; 两个物体都有碰撞器 Collider至少一个物体有刚体 Mass&#xff1a;质量 默认为千克&a…

postman自动化实战总结

Postman实战总结 简介 本次实战内容主要包括如下几点&#xff1a; l 背景介绍 l Postman使用&#xff0c;侧重于自动化实现&#xff0c;基础使用不做介绍 l 可视化Newman介绍 l 框架特色 l 实战中的坑 背景 随着国内软件技术的高速发展&#xff0c;越来越多的手工测试…

Qgis2threejs

three.js是JavaScript编写的一个开源的3D图形库&#xff0c;它可以用于创建各种各样的交互式3D应用程序和动画。该库提供了一系列的工具和功能&#xff0c;使得在Web浏览器中创建高质量的3D图形变得更加容易。 使用three.js&#xff0c;您可以轻松地创建3D场景&#xff0c;包括…

GoogleNet

Introduction 得益于深度学习的优势和更强大的卷积神经网络的出现&#xff0c;图像分类和目标检测的准确率发生了令人意想不到的进步。在2014年的ILSVRC比赛中&#xff0c;GoogLeNet取得了第一名的成绩&#xff0c;所用模型参数不足AlexNet&#xff08;2012年冠军&#xff09;…

基于Java医院住院管理系统设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a; ✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精…

记录使用gswin64.exe合并多个pdf为一个pdf

目录 下载gs920w64.exe 安装 配置相关信息 验证是否安装配置成功 合并多个pdf文件 下载gs920w64.exe https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/tag/gs920 ​ 安装 正常安装&#xff0c;记住自己的安装目录&#xff1a; ​ 配置相关信息 安装…

限流与令牌桶

一、概述 令牌桶是一种常用的流量控制技术。令牌桶本身没有丢弃和优先级策略。 原理 1&#xff0e;令牌以一定的速率放入桶中。 2&#xff0e;每个令牌允许源发送一定数量的比特。 3&#xff0e;发送一个包&#xff0c;流量调节器就要从桶中删除与包大小相等的令牌数。 4…

跟着《代码随想录刷题》(六)—— 二叉树

6.1 二叉树的前、中、后序遍历 144.、二叉树的前序遍历&#xff08;中左右&#xff09; LeetCode&#xff1a;114、二叉树的前序遍历 &#xff08;1&#xff09;递归法 class Solution { public:void traversal(TreeNode* cur, vector<int>& vec) {if (cur N…

eslint + prettier如何搭配使用

简介 eslintprettier写代码爽到飞起&#xff0c;既规范了代码的格式&#xff0c;同时也让你的代码美观易读。本文介绍如何在项目中使用eslint搭配prettier来规范你的代码 1.eslint 和 prettier区别 先来回答一个问题&#xff0c;eslint和prettier这二者有啥区别&#xff0c;es…

【后端面经-数据库】MySQL的事务隔离级别简介

【后端面经-数据库】MySQL的事务隔离级别简介 0. 事务的概念1. 三类问题2. 事务隔离级别3. 操作指令4. 总结5. 参考博文 0. 事务的概念 事务指的是一连串的集中操作指令&#xff0c;一个事务的执行必须执行完所有的动作才能算作执行结束。事务具有四个特点&#xff0c;简记作A…

表示学习(Representation Learning) Part2--Auto-Encoders、VAEs、GANs

文章目录 Compression:Auto-EncodersCapture parameter distribution (variance): Variational Auto-Encoders原理介绍数学推导生成数据diffusion modelsPros&Cons Train using a second network: GANs 来自Manolis Kellis教授&#xff08;MIT计算生物学主任&#xff09;的…

spark_idea

spark_idea 3、打jar包运行2、code1、pom_xml0、创建数据、模型、预测表0、Windows配置scala环境 3、打jar包运行 ./bin/spark-submit \--class spark02 \--master spark://hadoop102:7077 \--deploy-mode client \/home/gpb/scala_spark2.jar打jar包、存储到虚拟机中 编写执行…

什么是布隆过滤器?如何解决高并发缓存穿透问题?

日常开发中&#xff0c;大家经常使用缓存&#xff0c;但是你知道大型的互联网公司面对高并发流量&#xff0c;要注意缓存穿透问题吗!!! 本文会介绍布隆过滤器&#xff0c;空间换时间&#xff0c;以较低的内存空间、高效解决这个问题。 本篇文章的目录&#xff1a; 1、性能不…