【从零开始入门unity游戏开发之——C#篇41】C#迭代器(Iterator)——自定义类实现 foreach 操作
文章目录
- 前言
- 一、什么是迭代器?
- 二、标准迭代器的实现方法
- 1、自定义一个类`CustomList`
- 2、让CustomList继承IEnumerable接口
- 3、再继承IEnumerator接口
- 4、完善迭代器功能
- 5、**foreach遍历的本质**:
- 6、在Reset方法里把光标复原
- 三、用yield return语法糖实现迭代器
- 1、用yield return语法糖为普通类实现迭代器
- 2、用yield return语法糖为泛型类实现迭代器
- 四、总结
- 专栏推荐
- 完结
前言
前面我们使用过foreach 来遍历过如列表
、数组
等数据等,之所以可以这么做,其实就是它们内部已经帮我们实现了迭代器功能。
迭代器其实很像我们之前学过的自定义类排序——IComparable<T> 接口的实现
,思路是类似的。
一、什么是迭代器?
在 C# 中,迭代器(Iterator
)是一种特殊的方法,允许你在集合中按顺序逐个访问元素,而无需暴露集合的内部实现。通常,迭代器用于实现 foreach 循环。
C# 中有两种主要方式来实现迭代器:
IEnumerable<T>
和IEnumerator<T>
接口:这是实现迭代器的基础,通过实现这两个接口可以自定义迭代行为。yield
语法糖:这是 C# 提供的一种简化的方式来实现迭代器。
语法糖
是指某些语言特性或语法结构的简化,目的是提高代码的简洁性和可读性,同时不改变代码的语义或功能。
二、标准迭代器的实现方法
为了使自定义的类可以被foreach语句遍历,该类需要实现IEnumerable接口,并且通常还需要实现IEnumerator接口。IEnumerator接口提供了获取当前元素、移动到下一个元素以及重置位置的方法。通过实现这两个接口,我们能够控制如何遍历自定义类型的实例。
1、自定义一个类CustomList
class CustomList{
private int[] list;
public CustomList(){
list = new int[] {1, 2, 3, 4, 5};
}
}
现在直接使用foreach语句遍历CustomList肯定会报错,因为我们并没有实现迭代器
2、让CustomList继承IEnumerable接口
记得需要引入using System.Collections;
命名空间,接口要求必须实现GetEnumerator
方法
using System.Collections;
class CustomList : IEnumerable{
private int[] list;
public CustomList(){
list = new int[] {1, 2, 3, 4, 5};
}
public IEnumerator GetEnumerator()
{
}
}
但是其实继承IEnumerable接口都不重要,只要实现了GetEnumerator方法即可
但是为什么要继承呢?其实就是规定你严格实现GetEnumerator
方法,且这个方法也不好记
你会发现这时候,其实前面的foreach遍历就已经不报错了
现在执行遍历肯定还是走不通的,因为我们迭代器根本没实现任何内容
3、再继承IEnumerator接口
再继承IEnumerator接口,并实现里面的MoveNext
和Reset
方法和Current
属性
4、完善迭代器功能
声明一个index 光标,完善迭代器功能
class CustomList : IEnumerable, IEnumerator{
private int[] list;
//从-1开始的光标用于表示数据得到了哪个位置
private int index = -1;
public CustomList(){
list = new int[] {1, 2, 3, 4, 5};
}
public object Current => list[index];
public IEnumerator GetEnumerator()
{
//直接把自己返回即可
return this;
}
public bool MoveNext()
{
//移动光标
index++;
//是否溢出 溢出返回false
return index < list.Length;
}
public void Reset()
{
throw new NotImplementedException();
}
}
这时候前面foreach就可以打印出内容了
CustomList customList= new CustomList();
foreach (int item in customList){
Console.WriteLine(item);
}
结果
5、foreach遍历的本质:
- 先获取
in
后面这个对象的IEnumerator
,会调用对象其中的GetEnumerator
方法来获取IEnumerator
对象 - 执行这个
IEnumerator
对象中的MoveNext方法 - 只要
MoveNext
方法的返回值时true
就会去得到Current
的值然后赋值给item
6、在Reset方法里把光标复原
现在还差一个Reset
方法没有实现,这又有什么用呢?
比如如果我们需要遍历两次数据
CustomList customList= new CustomList();
foreach (int item in customList){
Console.WriteLine(item);
}
foreach (int item in customList){
Console.WriteLine(item);
}
结果
结果只打印了一次数据,因为我们的光标一直在加,超出索引,再继续打印MoveNext
一直返回false
,就没有数据了,所以我们需要在Reset
里把光标复原
public void Reset()
{
//重置光标
index = -1;
}
什么时候调用呢?在GetEnumerator
方法里调用即可,每次foreach开始会得到一次IEnumerator,且只会运行一次,我们可以写个打印验证这一点
public IEnumerator GetEnumerator()
{
Console.WriteLine("开始遍历");
Reset();
//直接把自己返回即可
return this;
}
结果,遍历两次,打印了两次数据,且每次遍历开始都仅调用一次GetEnumerator
方法获取IEnumerator
注:Reset
方法重置光标位置,一般写在获取IEumerator对象这个函数中,用于每次foreach遍历开始时先重置光标位置
三、用yield return语法糖实现迭代器
前面实现这个迭代器是不是感觉非常麻烦?所以C#专门提供了yield return
语法糖来帮助我们简化实现迭代器,yield return
会将当前值返回给调用者,并暂停执行,直到下次请求下一个值时继续。
1、用yield return语法糖为普通类实现迭代器
我们只需要继承IEnumerable
接口,实现GetEnumerator
方法即可
class CustomList : IEnumerable {
private int[] list;
public CustomList(){
list = new int[] {1, 2, 3, 4, 5};
}
public IEnumerator GetEnumerator()
{
for (int i = 0; i< list.Length; i++){
yield return list[i];
}
}
}
foreach 遍历打印
CustomList customList= new CustomList();
Console.WriteLine("第一次遍历");
foreach (int item in customList){
Console.WriteLine(item);
}
Console.WriteLine("第二次遍历");
foreach (int item in customList){
Console.WriteLine(item);
}
结果和前面一样,但是实现却方便了很多是不是
GetEnumerator
里其实也可以这么写,效果一样
public IEnumerator GetEnumerator()
{
// for (int i = 0; i< list.Length; i++){
// yield return list[i];
// }
yield return list[0];
yield return list[1];
yield return list[2];
yield return list[3];
yield return list[4];
}
但是通常肯定不会这么做,这里介绍这么写得方法,为了让你更容易理解yield return
的工作机制。
使用 yield return
的方法其实并没有创建一个完整的集合或数组,而是创建了一个延迟执行的状态机。每次调用迭代器方法时,都会从上一次暂停的位置继续执行。yield return
使得方法的执行过程可以暂停和恢复,这就是懒加载的本质。
你可以会问,前面不是说了foreach
每次遍历开始都仅调用一次GetEnumerator
方法获取IEnumerator
吗?这和yield return机制好像冲突了。
其实不然。在 foreach
循环内部,GetEnumerator
方法只会在循环开始时被调用一次。每次迭代时,foreach
使用的是同一个 IEnumerator
对象,这个对象负责管理 yield return
的暂停和恢复。本质其实和前面标准迭代器
的实现方法是一样的
2、用yield return语法糖为泛型类实现迭代器
泛型类实现其实也是一样,相信大家应该都懂了,这里就直接放出例子,大家参考参考
class CustomList<T> : IEnumerable
{
private T[] array;
public CustomList(params T[] array){
this.array = array;
}
public IEnumerator GetEnumerator()
{
for (int i = 0; i< array.Length; i++){
yield return array[i];
}
}
}
调用
CustomList<string> customList= new CustomList<string>("向", "宇", "的", "客", "栈");
Console.WriteLine("第一次遍历");
foreach (string item in customList){
Console.WriteLine(item);
}
Console.WriteLine("第二次遍历");
foreach (string item in customList){
Console.WriteLine(item);
}
结果
四、总结
迭代器就是可以让我们在外部直接通过foreach
遍历对象中元素而不需要了解其结构如何
主要的两种方式
- 传统方式继承两个接口实现里面的方法
- 用语法糖yield return去返回内容只需要继承一个接口即可
专栏推荐
地址 |
---|
【从零开始入门unity游戏开发之——C#篇】 |
【从零开始入门unity游戏开发之——unity篇】 |
【制作100个Unity游戏】 |
【推荐100个unity插件】 |
【实现100个unity特效】 |
【unity框架开发】 |
完结
赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注
,你的每一次支持
都是我不断创作的最大动力。当然如果你发现了文章中存在错误
或者有更好的解决方法
,也欢迎评论私信告诉我哦!
好了,我是向宇
,https://xiangyu.blog.csdn.net
一位在小公司默默奋斗的开发者,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!如果你遇到任何问题,也欢迎你评论私信或者加群找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~