更新记录

本文迁移自Panda666原博客,原发布时间:2021年6月28日。

一、先从可枚举类型讲起

1.1 什么是可枚举类型?



可枚举类型,可以简单的理解为:

有一个类,类中有挺多的数据,用一种统一的方式把他们列举出来。

在.NET中满足以下任意条件的都是可枚举类型:

Implements System.Collections.IEnumerable

Implements System.Collections.Generic.IEnumerable

Has a public parameterless method named GetEnumerator that returns an enumerator

这三条看起来非常的简单。第一条实现了IEnumerable接口就行了。第二条实现了IEnumerable接口就行了。第三条就更简洁了,实现了GetEnumerator方法就行了。所以可以发现,可枚举类型实现起来非常的方便。

1.2 那么问题来了,为什么需要可枚举类型?

上面不是说了吗,用一种统一的方式把数据列举出来。

那么问题来了,为什么需要用统一的方式取出数据?

因为方便取出数据,一百个类有一百个取出数据的方式,维护起来就很难受了。

所以统一数据的取出方式,是非常有必要的。

1.3 具体实现可枚举类型

说了这么多,是什么,为什么,来看看怎么用。我们先来看看,上面说到的2个接口。

System.Collections.IEnumerable接口的定义

  1. namespace System.Collections
  2. {
  3. public interface IEnumerable
  4. {
  5. IEnumerator GetEnumerator();
  6. }
  7. }

System.Collections.Generic.IEnumerable接口 的定义

  1. public interface IEnumerable<out T> : IEnumerable
  2. {
  3. IEnumerator<T> GetEnumerator();
  4. }

注意点:

可以看到2个接口大同小异,都有GetEnumerator方法,但要注意的是2个方法的返回类型是不同的。

GetEnumerator从字面意思也能看出来,表示获得Enumerator。

那么什么是Enumerator?Enumerator表示枚举器。

我们的数据统一数据的取出方式全部都靠枚举器帮我们代劳,而不是直接定义在本类中,当然你也可以这样做,但不建议。接下来现在我们来定义几个可枚举类型。

继承自IEnumerable的可枚举类型

  1. public class PandaClass1 : IEnumerable
  2. {
  3. public IEnumerator GetEnumerator()
  4. {
  5. //内部没有具体实现,所以先抛出异常
  6. throw new NotImplementedException();
  7. }
  8. }

继承自IEnumerable的可枚举类型

  1. public class PandaClass2 : IEnumerable<int>
  2. {
  3. public IEnumerator<int> GetEnumerator()
  4. {
  5. //内部没有具体实现,所以先抛出异常
  6. throw new NotImplementedException();
  7. }
  8. IEnumerator IEnumerable.GetEnumerator()
  9. {
  10. //内部没有具体实现,所以先抛出异常
  11. throw new NotImplementedException();
  12. }
  13. }

直接实现GetEnumerator方法的可枚举类型

  1. public class PandaClass3
  2. {
  3. public IEnumerator GetEnumerator()
  4. {
  5. //内部没有具体实现,所以先抛出异常
  6. throw new NotImplementedException();
  7. }
  8. }

直接实现GetEnumerator方法的可枚举类型

  1. public class PandaClass4
  2. {
  3. public IEnumerator<int> GetEnumerator()
  4. {
  5. //内部没有具体实现,所以先抛出异常
  6. throw new NotImplementedException();
  7. }
  8. }

到这里我们已经搞定可枚举类型了,怎么进行枚举?

二、有趣的枚举器(Enumerator)

2.1 那么问题来了,什么是枚举器。

Enumerator在英文中有列举出人或物的意思。所以是列举器的意思?不急,我们来对比看看程序中什么是枚举器。

在.NET中满足以下任意条件的都是枚举器:

1、Implements System.Collections.IEnumerator

2、Implements System.Collections.Generic.IEnumerator

3、Has a public parameterless method named MoveNext and property called Current

第三条中的实现MoveNext方法和Current属性倒是挺好理解的,只要实现了这2个成员就叫枚举器。

比如下面这类就叫枚举器

  1. public class Enumerator
  2. {
  3. public IteratorVariableType Current { get {...} }
  4. public bool MoveNext() {...}
  5. }

那么第一二条中的接口是什么,不慌,我们先来看看这2个接口的定义。

IEnumerator接口的定义

  1. namespace System.Collections
  2. {
  3. public interface IEnumerator
  4. {
  5. object Current { get; }
  6. bool MoveNext();
  7. void Reset();
  8. }
  9. }

IEnumerator接口的定义

  1. namespace System.Collections.Generic
  2. {
  3. public interface IEnumerator<out T> : IEnumerator, IDisposable
  4. {
  5. T Current { get; }
  6. }
  7. }

需要注意的点:

IEnumerator接口返回的是object类型。

从接口的定义我们可以发现IEnumerator继承自IEnumerator。

所以IEnumerator是同样包含的有MoveNext()和Reset()方法的。

MoveNext()方法表示移动到下一个元素。

Reset()方法表示重置枚举器。

Current属性用于获得当前的元素值。

2.2 实现枚举器

直接继承IEnumerator接口实现枚举器

  1. public class PandaEnumerator1 : IEnumerator
  2. {
  3. //内部没有具体实现,所以先抛出异常
  4. public object Current => throw new NotImplementedException();
  5. public bool MoveNext()
  6. {
  7. //内部没有具体实现,所以先抛出异常
  8. throw new NotImplementedException();
  9. }
  10. public void Reset()
  11. {
  12. //内部没有具体实现,所以先抛出异常
  13. throw new NotImplementedException();
  14. }
  15. }

直接继承IEnumerator接口实现枚举器

  1. public class PandaEnumerator2 : IEnumerator<int>
  2. {
  3. //内部没有具体实现,所以先抛出异常
  4. public int Current => throw new NotImplementedException();
  5. //内部没有具体实现,所以先抛出异常
  6. object IEnumerator.Current => throw new NotImplementedException();
  7. //用于释放枚举器使用的资源
  8. public void Dispose()
  9. {
  10. throw new NotImplementedException();
  11. }
  12. public bool MoveNext()
  13. {
  14. //内部没有具体实现,所以先抛出异常
  15. throw new NotImplementedException();
  16. }
  17. public void Reset()
  18. {
  19. //内部没有具体实现,所以先抛出异常
  20. throw new NotImplementedException();
  21. }
  22. }

直接实现要求的方法实现枚举器

  1. public class PandaEnumerator3
  2. {
  3. //内部没有具体实现,所以先抛出异常
  4. public object Current => throw new NotImplementedException();
  5. public bool MoveNext()
  6. {
  7. //内部没有具体实现,所以先抛出异常
  8. throw new NotImplementedException();
  9. }
  10. public void Reset()
  11. {
  12. //内部没有具体实现,所以先抛出异常
  13. throw new NotImplementedException();
  14. }
  15. }

三、组合枚举类型和枚举器

那么问题来了,枚举类型和枚举器都搞定了。怎么把他们组合起来?怎么用来?

我们这里用一个实例来进行演示。我们先定义一个学生类来表示要枚举的数据。

  1. /// <summary>
  2. /// 学生数据
  3. /// </summary>
  4. public class Student
  5. {
  6. /// <summary>
  7. /// 编号
  8. /// </summary>
  9. public string Id { get; set; }
  10. /// <summary>
  11. /// 姓名
  12. /// </summary>
  13. public string Name { get; set; }
  14. /// <summary>
  15. /// 年龄
  16. /// </summary>
  17. public int Age { get; set; }
  18. }

然后我们定义枚举器

  1. /// <summary>
  2. /// 枚举器
  3. /// </summary>
  4. public class StudentIEnumerator : IEnumerator<Student>
  5. {
  6. /// <summary>
  7. /// 用于遍历的学生信息
  8. /// </summary>
  9. private List<Student> InnerStudents { get; set; }
  10. /// <summary>
  11. /// 当前的位置
  12. /// </summary>
  13. private int CurrentPosition { get; set; }
  14. public StudentIEnumerator(List<Student> students)
  15. {
  16. this.InnerStudents = students;
  17. this.CurrentPosition = -1;
  18. }
  19. //获得当前的元素
  20. public Student Current => this.InnerStudents[this.CurrentPosition];
  21. //如果无需兼容老代码,可以无需修改
  22. object IEnumerator.Current => throw new NotImplementedException();
  23. public void Dispose()
  24. {
  25. }
  26. public bool MoveNext()
  27. {
  28. if(this.CurrentPosition < this.InnerStudents.Count-1)
  29. {
  30. ++this.CurrentPosition;
  31. return true;
  32. }
  33. return false;
  34. }
  35. public void Reset()
  36. {
  37. this.CurrentPosition = -1;
  38. }
  39. }

最后定义可枚举类型

  1. /// <summary>
  2. /// 可枚举类型
  3. /// </summary>
  4. public class StudentCollecion : IEnumerable<Student>
  5. {
  6. /// <summary>
  7. /// 学生数据
  8. /// </summary>
  9. public List<Student> Students { get; set; }
  10. public StudentCollecion()
  11. {
  12. //初始化List
  13. this.Students = new List<Student>();
  14. //虚构用于测试的学生信息
  15. this.Students.Add(new Student() {
  16. Id = "666",
  17. Name = "Panda",
  18. Age = 18
  19. });
  20. this.Students.Add(new Student()
  21. {
  22. Id = "888",
  23. Name = "Donkey",
  24. Age = 18
  25. });
  26. this.Students.Add(new Student()
  27. {
  28. Id = "999",
  29. Name = "Dog",
  30. Age = 18
  31. });
  32. this.Students.Add(new Student()
  33. {
  34. Id = "777",
  35. Name = "Pig",
  36. Age = 18
  37. });
  38. this.Students.Add(new Student()
  39. {
  40. Id = "333",
  41. Name = "Monkey",
  42. Age = 18
  43. });
  44. }
  45. /// <summary>
  46. /// 获得枚举器
  47. /// 返回我们自定义的配套枚举器
  48. /// </summary>
  49. /// <returns></returns>
  50. public IEnumerator<Student> GetEnumerator()
  51. {
  52. return new StudentIEnumerator(this.Students);
  53. }
  54. //如果无需兼容老代码可以不用修改
  55. IEnumerator IEnumerable.GetEnumerator()
  56. {
  57. throw new NotImplementedException();
  58. }
  59. }

四、使用可枚举类型

那么问题来了,怎么使用可枚举类型?

方式一:使用foreach。这是最常用的方式。

  1. StudentCollecion students = new StudentCollecion();
  2. foreach (var student in students)
  3. {
  4. Console.WriteLine(student.Id);
  5. }

注意:foreach只是方式二的语法糖。如果枚举器实现了IDisposable接口,foreach会自动调用dispose方法。

方式二:直接使用迭代器

  1. StudentCollecion students = new StudentCollecion();
  2. //获得实例的枚举器
  3. IEnumerator<Student> enumerator = students.GetEnumerator();
  4. //手动操作
  5. while (enumerator.MoveNext())
  6. {
  7. //获得元素值
  8. Console.WriteLine(enumerator.Current.Name);
  9. }
  10. //重置一下枚举器
  11. enumerator.Reset();

五、总结

实例完整源代码

  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. namespace Panda666comTest
  5. {
  6. /// <summary>
  7. /// 枚举器
  8. /// </summary>
  9. public class StudentIEnumerator : IEnumerator<Student>
  10. {
  11. /// <summary>
  12. /// 用于遍历的学生信息
  13. /// </summary>
  14. private List<Student> InnerStudents { get; set; }
  15. /// <summary>
  16. /// 当前的位置
  17. /// </summary>
  18. private int CurrentPosition { get; set; }
  19. public StudentIEnumerator(List<Student> students)
  20. {
  21. this.InnerStudents = students;
  22. this.CurrentPosition = -1;
  23. }
  24. //获得当前的元素
  25. public Student Current => this.InnerStudents[this.CurrentPosition];
  26. //如果无需兼容老代码,可以无需修改
  27. object IEnumerator.Current => throw new NotImplementedException();
  28. public void Dispose()
  29. {
  30. }
  31. public bool MoveNext()
  32. {
  33. if (this.CurrentPosition < this.InnerStudents.Count - 1)
  34. {
  35. ++this.CurrentPosition;
  36. return true;
  37. }
  38. return false;
  39. }
  40. public void Reset()
  41. {
  42. this.CurrentPosition = -1;
  43. }
  44. }
  45. /// <summary>
  46. /// 学生数据
  47. /// </summary>
  48. public class Student
  49. {
  50. /// <summary>
  51. /// 编号
  52. /// </summary>
  53. public string Id { get; set; }
  54. /// <summary>
  55. /// 姓名
  56. /// </summary>
  57. public string Name { get; set; }
  58. /// <summary>
  59. /// 年龄
  60. /// </summary>
  61. public int Age { get; set; }
  62. }
  63. /// <summary>
  64. /// 可枚举类型
  65. /// </summary>
  66. public class StudentCollecion : IEnumerable<Student>
  67. {
  68. /// <summary>
  69. /// 学生数据
  70. /// </summary>
  71. public List<Student> Students { get; set; }
  72. public StudentCollecion()
  73. {
  74. //初始化List
  75. this.Students = new List<Student>();
  76. //虚构用于测试的学生信息
  77. this.Students.Add(new Student()
  78. {
  79. Id = "666",
  80. Name = "Panda",
  81. Age = 18
  82. });
  83. this.Students.Add(new Student()
  84. {
  85. Id = "888",
  86. Name = "Donkey",
  87. Age = 18
  88. });
  89. this.Students.Add(new Student()
  90. {
  91. Id = "999",
  92. Name = "Dog",
  93. Age = 18
  94. });
  95. this.Students.Add(new Student()
  96. {
  97. Id = "777",
  98. Name = "Pig",
  99. Age = 18
  100. });
  101. this.Students.Add(new Student()
  102. {
  103. Id = "333",
  104. Name = "Monkey",
  105. Age = 18
  106. });
  107. }
  108. /// <summary>
  109. /// 获得枚举器
  110. /// 返回我们自定义的配套枚举器
  111. /// </summary>
  112. /// <returns></returns>
  113. public IEnumerator<Student> GetEnumerator()
  114. {
  115. return new StudentIEnumerator(this.Students);
  116. }
  117. //如果无需兼容老代码可以不用修改
  118. IEnumerator IEnumerable.GetEnumerator()
  119. {
  120. throw new NotImplementedException();
  121. }
  122. }
  123. class Program
  124. {
  125. static void Main(string[] args)
  126. {
  127. //实例化可枚举类型
  128. StudentCollecion students = new StudentCollecion();
  129. //方式一:使用foreach来遍历可枚举类型
  130. foreach (var student in students)
  131. {
  132. Console.WriteLine(student.Id);
  133. }
  134. //方式二:使用手动调用枚举器
  135. //获得实例的枚举器
  136. IEnumerator<Student> enumerator = students.GetEnumerator();
  137. //手动操作
  138. while (enumerator.MoveNext())
  139. {
  140. //获得元素值
  141. Console.WriteLine(enumerator.Current.Name);
  142. }
  143. //重置一下枚举器
  144. enumerator.Reset();
  145. enumerator.Dispose();
  146. //wait
  147. Console.WriteLine("Success");
  148. Console.ReadKey();
  149. }
  150. }
  151. }

C#中的枚举器的更多相关文章

  1. ruby中迭代器枚举器的理解

    参考<ruby编程语言>5.3迭代器和可枚举对象 迭代器一个迭代器是一个方法,这个方法里面有yield语句,这个方法里的yield语句,与传递给这个方法的块进行数据传输 yield将数据传 ...

  2. C#中的枚举器(转)

    术语表 Iterator:枚举器(迭代器) 如果你正在创建一个表现和行为都类似于集合的类,允许类的用户使用foreach语句对集合中的成员进行枚举将会是很方便的.这在C# 2.0中比 C# 1.1更容 ...

  3. C#图解教程 第十八章 枚举器和迭代器

    枚举器和迭代器 枚举器和可枚举类型 foreach语句 IEnumerator接口 使用IEnumerable和IEnumerator的示例 泛型枚举接口迭代器 迭代器块使用迭代器来创建枚举器使用迭代 ...

  4. JAVA中的数据结构——集合类(序):枚举器、拷贝、集合类的排序

    枚举器与数据操作 1)枚举器为我们提供了访问集合的方法,而且解决了访问对象的“数据类型不确定”的难题.这是面向对象“多态”思想的应用.其实是通过抽象不同集合对象的共同代码,将相同的功能代码封装到了枚举 ...

  5. C#中的foreach语句与枚举器接口(IEnumerator)及其泛型 相关问题

    这个问题从<C#高级编程>数组一节中的foreach语句(6.7.2)发现的. 因为示例代码与之前的章节连贯,所以我修改了一下,把自定义类型改为了int int[] bs = { 2, 3 ...

  6. C#2.0中使用yield关键字简化枚举器的实现

    我们知道要使用foreach语句从客户端代码中调用迭代器,必需实现IEnumerable接口来公开枚举器,IEnumerable是用来公开枚举器的,它并不实现枚举器,要实现枚举器必需实现IEnumer ...

  7. Python 中的枚举类型~转

    Python 中的枚举类型 摘要: 枚举类型可以看作是一种标签或是一系列常量的集合,通常用于表示某些特定的有限集合,例如星期.月份.状态等. 枚举类型可以看作是一种标签或是一系列常量的集合,通常用于表 ...

  8. for..in遍历,枚举器

    #pragma mark ------------for循环遍历集合中的元素------ //创建一个数组,包含5个字符串对象,倒序取出数组中的所有元素,并存储到另一可变数组中 NSArray *ar ...

  9. ruby迭代器枚举器

    迭代器一个迭代器是一个方法,这个方法里面有yield语句,使用了yield的方法叫做迭代器,迭代器并非一定要迭代,与传递给这个方法的块进行数据传输 yield将数据传给代码快,代码块再把数据传输给yi ...

随机推荐

  1. Java基础之浅谈接口

    前言 前几篇文章我们已经把Java的封装.继承.多态学习完了,现在我们开始比较便于我们实际操作的学习,虽然它也是Java基础部分,但是其实入门容易,精通很难. 我认真的给大家整理了一下这些必须学会.了 ...

  2. spring原始注解开发-01

    我们使用xml-Bean标签的配置方式和注解做对比理解 1.创建UserDao接口以及UserDao的实现类UserDaoImpl(接口代码省略) public class UserDaoImpl i ...

  3. .NET 6 史上最全攻略

    欢迎使用.NET 6.今天的版本是.NET 团队和社区一年多努力的结果.C# 10 和F# 6 提供了语言改进,使您的代码更简单.更好.性能大幅提升,我们已经看到微软降低了托管云服务的成本..NET ...

  4. instanceof 和类型转换

    instanceof 和类型转换 instanceof 判断a 和 B 类型是否相似 公式 System.out.println(a instanceof B); //true / false 编译是 ...

  5. NodeJS学习day1

    今天主要学习的IO操作 const fs = require('fs') fs.readFile('./files/11.txt','utf-8',function(err,daraStr){ //读 ...

  6. Istio实践(2)-流量控制及服务间调用

    前言:接上一篇istio应用部署,本文介绍通过virtualservice实现流量控制,并通过部署client端进行服务调用实例 1. 修改virtualservice组件,实现权重占比访问不同版本服 ...

  7. vue下一代状态管理Pinia.js 保证你看的明明白白!

    1.pinia的简单介绍 Pinia最初是在2019年11月左右重新设计使用Composition API的 Vue 商店外观的实验. 从那时起,最初的原则相同,但 Pinia 适用于 Vue 2 和 ...

  8. Linux-SUID提权

    前言 最近打靶场的时候最后都会涉及到提权,所以想着总结一下. SUID提权原理 SUID(设置用户ID)是赋予文件的一种权限,它会出现在文件拥有者权限的执行位上,具有这种权限的文件会在其执行时,使调用 ...

  9. Lab_1:练习1——理解通过make生成执行文件的过程

    lab_0 清华大学ucore实验环境配置详细步骤!(小白入) lab_1 清华大学ucore bootload启动ucore os(预备知识) Lab_1:练习1--理解通过make生成执行文件的过 ...

  10. 【面试普通人VS高手系列】说说缓存雪崩和缓存穿透的理解,以及如何避免?

    听说10个人去互联网公司面试,有9个人会被问到缓存雪崩和缓存穿透的问题. 听说,这9个人里面,至少有8个人回答得不完整. 而这8个人里面,全都是在网上找的各种面试资料去应付的,并没有真正理解. 当然, ...