当前位置: 首页 > news >正文

第19章 枚举器和迭代器 笔记

第19章 枚举器和迭代器 25.08.04

19.1 枚举器和可枚举类型

使用 foreach 语句

下面是使用 foreach 语句遍历数组的示例。

int[] arr1 = { 10, 11, 12, 13 }; // Define the array.
foreach (int item in arr1) // Enumerate the elements.Console.WriteLine($"Item value: { item }");// output
Item value: 10
Item value: 11
Item value: 12
Item value: 13

数组可以使用这种方式访问,是因为:数组提供了枚举器对象。枚举器知道数组中元素的次序,并依次返回数组中的元素。

对于有枚举器的类型而言,使用 GetEnumerator 方法来获取其拥有的枚举器,实现该方法的类型称为可枚举类型。数组就是可枚举类型。

在这里插入图片描述

foreach 结构设计用来和可枚举类型一起使用,如下行为会被执行:

  1. 调用 GetEnumerator 方法获取对象的枚举器。
  2. 从枚举起中请求每一项并作为迭代变量(可读不可写)。
foreach( Type VarName in EnumerableObject )
{// ...
}

19.2 IEnumerator 接口

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

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

  • 为只读属性。

  • 返回 object 类型的引用。

2.MoveNext :是将枚举器位置前进到集合中下一项的方法,返回布尔值。

  • 如果新的位置有效,返回 true。
  • 如果新的位置无效,返回 false。

3.Reset:将位置重置为原始状态。

  • 枚举器的原始位置在序列中的第一项之前,因此 MoveNext 必须在第一次使用 Current 之前调用。

在这里插入图片描述

在编写 foreach 循环的时候,C# 编译器将生成与下面十分类似的代码(以 CIL 的形式)。

static void Main()
{int[] arr1 = { 10, 11, 12, 13 }; // Create an array.// Get and store the enumerator.IEnumerator ie = arr1.GetEnumerator();// Move to the next position.while ( ie.MoveNext() ){ // Get the current item.int item = (int) ie.Current;Console.WriteLine($"Item value: { item }"); // Write it out.}
}// output
Item value: 10
Item value: 11
Item value: 12
Item value: 13

19.3 IEnumerable 接口

可枚举类是指实现了 IEnumerable 接口的类,该接口只有一个成员–GetEnumerator,返回对象的枚举器。

在这里插入图片描述

using System.Collections;
// Implements the IEnumerable interface
class MyClass : IEnumerable
{//  Returns an object of type IEnumeratorpublic IEnumerator GetEnumerator { ... }// ... 
}

使用 IEnumerable 和 IEnumerator 的示例

using System;
using System.Collections;
class ColorEnumerator : IEnumerator
{string[] Colors;int Position = -1;public ColorEnumerator( string[] theColors ) // Constructor{Colors = new string[theColors.Length];for ( int i = 0; i < theColors.Length; i++ )Colors[i] = theColors[i];}public object Current // Implement Current.{get{if ( Position == -1 )throw new InvalidOperationException();if ( Position >= Colors.Length )throw new InvalidOperationException();return Colors[Position];}}public bool MoveNext() // Implement MoveNext.{if ( Position < Colors.Length - 1 ){Position++;return true;}elsereturn false;}public void Reset() // Implement Reset.{Position = -1;}
}class Spectrum : IEnumerable
{string[] Colors = { "violet", "blue", "cyan", "green", "yellow", "orange", "red" };public IEnumerator GetEnumerator(){return new ColorEnumerator( Colors );}
}
class Program
{static void Main(){Spectrum spectrum = new Spectrum();foreach ( string color in spectrum )Console.WriteLine( color );}
}// output
violet
blue
cyan
green
yellow
orange
red

19.4 泛型枚举接口

使用 C# 泛型和非泛型的方式相差不大。

本质区别如下:

  • 对于非泛型接口形式:
    • IEnumerable 接口的 GetEnumerator 方法返回实现 IEnumerator 的枚举器类实例。
    • 实现 IEnumerator 的类实现了 Current 属性,返回 object 类型的引用,然后将其转化为对象的实际类型。
  • 对于泛型接口形式:
    • IEnumerable 接口的 GetEnumerator 方法返回实现 IEnumerator 的枚举器类实例。
    • 实现 IEnumerator 的类实现了 Current 属性,返回实际类型的实例,而不是 object 基类的引用。
    • 实际上泛型接口的声明是协变的,即 IEnumerable 和 IEnumerator,因此这些接口的对象可以是派生的类型。

泛型版本简单易用,但其结构略显复杂。

在这里插入图片描述

19.5 迭代器

可枚举类和枚举器在 .NET 集合类中被广泛使用,从 C# 2.0 版本开始提供了创建枚举器和可枚举类型更简单的方式,将这种结构称为迭代器

在下面这个示例中:

  • 迭代器返回一个泛型枚举器,该枚举器返回 3 个 string 类型的项。
  • yield return 语句声明这是枚举中的下一项。
// 返回泛型枚举器,它返回的是字符串对象
public IEnumerator<string>BlackAndWhite() // 版本1
{yield return "black";  //yield returnyield return "gray";   //yield returnyield return "white";  //yield return
}

下面的方法声明了另一个版本,输出结果与上面相同。

// 返回泛型枚举器,它返回的是字符串对象
public IEnumerator<string>BlackAndwhite()  // 版本2
{string[]theColors "black","gray","white"}for (int i=0;i theColors.Length;i++)yield return theColors[i];       //yield return
}

枚举器不会一次返回所有的元素,而是每次访问 Current 属性时返回一个新值。

19.5.1 迭代器块

迭代器块是有一个或多个 yield 语句的代码块,可以是如下任意一种代码块:

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

迭代器块描述了希望编译器为我们创建的枚举器类的行为,其中的代码描述了如何枚举元素。

  1. yield return:指定序列中要返回的下一项。
  2. yield break:指定在序列中没有其他项。

编译器得到有关枚举项的描述后,会构建包含所有需要的方法(MoveNext)和属性(Current)实现的枚举器类,产生的类被嵌套包含在声明迭代器的类中。

根据指定的返回类型,可以让迭代器产生枚举器或可枚举类型

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

19.5.2 使用迭代器来创建枚举器

class MyClass
{public IEnumerator<string> GetEnumerator(){ return BlackAndWhite(); } // Returns the enumerator// Returns an enumeratorpublic IEnumerator<string> BlackAndWhite() // Iterator{yield return "black";yield return "gray";yield return "white";}
}
class Program
{static void Main(){MyClass mc = new MyClass();// Use the instance of MyClass.foreach (string shade in mc)Console.WriteLine(shade);}
}// output
black
gray
white

图中左边演示了返回类型是 IEnumerator。

图中右边演示了它有一个嵌套类实现了 IEnumerator。

在这里插入图片描述

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

本节例子使用迭代器来创建可枚举类型,而不是枚举器。

  • BlackAndWhite 迭代器方法返回 IEnumerable 而不是 IEnumerator。
  • MyClass 首先调用 BlackAndWhite 方法获取可枚举类型对象,然后调用该对象的 GetEnumerator 方法来获取结果,从而实现 GetEnumerator 方法。
class MyClass
{public IEnumerator<string> GetEnumerator(){IEnumerable<string> myEnumerable = BlackAndWhite(); // Get enumerable.return myEnumerable.GetEnumerator(); // Get enumerator.} // Returns an enumerablepublic IEnumerable<string> BlackAndWhite(){yield return "black";yield return "gray";yield return "white";}
}class Program
{static void Main(){MyClass mc = new MyClass();// Use the class object.foreach (string shade in mc)Console.Write($"{ shade } ");// Use the class iterator method.foreach (string shade in mc.BlackAndWhite())Console.Write($"{ shade } ");}
}// output
black gray white black gray white
  • 图中左边演示了返回类型是 IEnumerable。
  • 图中右边演示了它有一个嵌套类实现了 IEnumerator 和 IEnumerable。

在这里插入图片描述

编译器生成的类是可枚举类型并返回一个枚举器。编译器还生成了方法 BlackAndWhite,返回可枚举对象

19.6 常见迭代器模式

  1. 实现返回枚举器的迭代器。

    通过实现 GetEnumerator 方法让类可枚举,它返回由迭代器返回的枚举器。

  2. 实现返回可枚举类型的迭代器。

    实现 GetEnumerator 来让类本身可枚举;或不实现,来让类不可枚举。

常见的迭代器模式

// 枚举器的迭代器模式
class MyClass
{public IEnumerator<string> GetEnumerator(){return IteratorMethod();}public IEnumerator<string> IteratorMethod(){...yield return ...;}...
}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 ...;}...
}Main
{MyClass mc = new MyClass();foreach( string x in mc )...foreach( string x in mc.IteratorMethod() )...
}

19.7 产生多个可枚举类型

Spectrum 类有两个可枚举类型的迭代器,但类本身不可枚举,因为没有实现 GetEnumerator 方法。

using System;
using System.Collections.Generic;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(){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();}
}// output
violet blue cyan green yellow orange red
red orange yellow green cyan blue violet

19.8 将迭代器作为属性

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

  1. 使用迭代器产生两个枚举器的类。
  2. 演示迭代器如何实现为属性,而不是方法。

这段代码声明了两个属性来定义两个不同的枚举器。GetEnumerator 方法根据 _listFromUVtoIR 布尔变量的值返回两个枚举器中的一个。如果 _listFromUVtoIR 为 true,则返回 UVtoIR 枚举器;否则,返回 IRtoUV 枚举器。

using System;
using System.Collections.Generic;class Spectrum 
{private bool _listFromUVtoIR;private string[] colors = { "violet", "blue", "cyan", "green", "yellow", "orange", "red" };public Spectrum(bool listFromUVtoIR){_listFromUVtoIR = listFromUVtoIR;}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(){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();}
}

19.9 迭代器的实质

有关迭代器的其他重要事项:

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

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

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

在这里插入图片描述

http://www.lryc.cn/news/610789.html

相关文章:

  • Spring小细节
  • MySQL连接解决:“Host is not allowed to connect to this MySQL server”错误详解
  • HTML总结全览
  • 解决错误nvcc fatal : Unsupported gpu architecture ‘compute_86‘
  • ESOP-3D系统实现机械设备生产流程的可追溯性
  • 人工智能领域、图欧科技、IMYAI智能助手2025年5月更新月报
  • 树状数组的性质
  • AI 对话高效输入指令攻略(四):AI+Apache ECharts:生成各种专业图表
  • C++ ---》string类的模拟实现
  • Solidity智能合约基础
  • 单目云台双摄像头配置双摄像头的优势
  • 深入理解 Android SO 导出符号:机制与安全优化
  • Spring 的优势
  • 应急响应排查思路
  • 市场与销售协同:CRM如何打破部门数据孤岛?
  • 8.5 CSS3多列布局
  • 深入解析RNN神经网络原理与应用
  • GitCode新手使用教程
  • RabbitMQ面试精讲 Day 11:RabbitMQ集群架构与节点类型
  • 人工智能之数学基础:利用全概率公式如何将复杂事件转为简单事件
  • 大模型|极简说清“数据并行”
  • AcWing 3690:求交点 ← 复旦大学考研机试题 + 克莱姆法则
  • 嵌入式开发学习———Linux环境下IO进程线程学习(四)
  • Python爬虫09_Requests用bs4进行数据解析
  • selenium自动化收集资料
  • linux服务器上word转pdf后乱码问题
  • In-memory不要全加载怎么做?
  • 基于LDA主题的网络舆情与情感分析——以云南某景区话题为例
  • 本机部署K8S集群
  • 基于k8s环境下的pulsar常用命令(上)