2021年了,`IEnumerator`、`IEnumerable`还傻傻分不清楚?
- IEnumerator接口
- IEnumerable接口
- 最佳实践
- foreach 实质、 微软官方信源
- 20230817补充
IEnumerator
、IEnumerable
这两个接口单词相近、含义相关,傻傻分不清楚。
入行多年,一直没有系统性梳理这对李逵李鬼。
最近本人在怼着why神的《其实吧,LRU也就那么回事》,方案1使用数组实现LUR,手写算法涉及这一对接口,借此机会本次覆盖这一对难缠的冤家。
1. IEnumerator 接口
IEnumerator、IEnumerable接口有相似的名称,这两个接口通常也在一起使用,它们有不同的用途。
IEnumerator接口为类内部的集合提供了迭代姿势, IEnumerator 要求你实现三个方法:
MoveNext
方法: 该方法将集合索引加1,并返回一个bool值,指示是否已到达集合的末尾。Reset
方法: 它将集合索引重置为其初始值-1,这会使枚举数无效。Current
方法: 返回position
位置的当前对象
2. IEnumerable 接口
IEnumerable接口为foreach
迭代提供了支持,IEnumerable申明集合具备可迭代的性质。
要求列表实现GetEnumerator
方法。
public IEnumerator GetEnumerator() { return (IEnumerator)this; }
该用哪一个接口?
仅凭以上辞藻,很难区分两个接口的使用场景。
IEnumerator接口提供了对类中的集合类型对象的迭代,
IEnumerable接口允许使用foreach循环进行枚举, 我可以被迭代,具体的迭代姿势由上面的接口定义
但是,IEnumerable
接口的GetEnumerator
方法会返回一个IEnumerator
接口。要实现IEnumerable
,你还必须实现IEnumerator
。
从英文词根上讲:
IEnumerator接口代表了枚举器,里面定义了枚举姿势;
IEnumerable接口代表该对象具备了可被枚举的性质。
3. 最佳实践
- 在类中嵌套实现IEnumerator枚举器,你甚至可以创建多个枚举器。
- 为IEnumerator枚举器的
Current
方法提供异常处理。
为什么要这么做?
如果集合的内容发生变化,则reset
方法将被调用,紧接着当前枚举数无效,您将收到一个IndexOutOfRangeException
异常(其他情况也可能导致此异常)。
所以执行一个Try…Catch块来捕获这个异常并引发InvalidOperationException
异常, 提示在迭代时不允许修改集合内容。
这也正是我们常见的在foreach 里面尝试修改迭代对象会报
InvalidOperationException
异常的原因。
下面以汽车列表为例实现IEnumerator IEnumerable接口
public class Car { public string Brand { get; set; } public int Age { get; set; } public Car(string brand, int age) { Brand = brand; Age = age; } } // 可被迭代的集合类 public class Cars : IEnumerable { private readonly Car[] Carlist; public Cars() { Carlist = new Car[6] { new Car("Ford",1992), new Car("Fiat",1988), new Car("Buick",1932), new Car("Ford",1932), new Car("Dodge",1999), new Car("Honda",1977) }; } public IEnumerator GetEnumerator() { return new MyEnumerator(Carlist); } // 内置的MyEnumerator 枚举器 private class MyEnumerator : IEnumerator { public Car[] Carlist; int position = -1; public MyEnumerator(Car[] list) { Carlist = list; } public bool MoveNext() { position++; return (position < Carlist.Length); } public void Reset() { position = -1; } public object Current { get { try { return Carlist[position]; } catch (IndexOutOfRangeException) { throw new InvalidOperationException(); } } } } }
使用姿势:
public static void Main() { var cars = new Cars(); foreach (Car item in cars) { Console.WriteLine($"{item.Brand}:{item.Age}"); } } --- Ford:1992 Fiat:1988 Buick:1932 Ford:1932 Dodge:1999 Honda:1977
4. foreach编译实质
在foreach cars
的时候,被编译器翻译成如下代码:
List<Car>.Enumerator e = cars.GetEnumerator(); try { while (e.MoveNext()) { int v = e.Current; Console.WriteLine(v); // «embedded_statement» 要重复执行的语句。 } } finally { IDisposable d = e as IDisposable; if (d != null) d.Dispose(); }
这里面要注意:
(1) v是在循环体内定义,是妥妥的块内部变量。 https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/statements#1295-the-foreach-statement
2023/08/17 补充
(2) 变量e
已经脱离变量cars
, 故即使此时cars
被重新指向,也不会影响foreach迭代。
但是若此时修改变量cars
, 则会直接引用内部的Carlist数组
产生变动,foreach会爆异常。
本文来自博客园,作者:{有态度的马甲},转载请注明原文链接:https://www.cnblogs.com/JulianHuang/p/14271285.html
欢迎关注我的原创技术、职场公众号, 加好友谈天说地,一起进化
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?