前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >迭代器模式 与 C# IEnumerator/IEnumerable

迭代器模式 与 C# IEnumerator/IEnumerable

作者头像
jgrass
发布2024-12-25 16:07:38
发布2024-12-25 16:07:38
20100
代码可运行
举报
文章被收录于专栏:蔻丁杂记蔻丁杂记
运行总次数:0
代码可运行

迭代器模式 与 C# IEnumerator/IEnumerable

Part1 迭代器模式 与 接口

IEnumerable

IEnumerator

代码语言:javascript
代码运行次数:0
运行
复制
interface IEnumerable{    IEnumerator GetEnumerator();}
// 泛型版本 : IEnumerator<T>interface IEnumerator{    object Current { get; }    bool MoveNext();    void Reset();}

这两个接口用于实现 迭代器 这种设计模式。

迭代器模式:

在软件构建过程中,集合对象内部结构常常变化各异。但对于这些集合对象,我们希望在不暴露其内部结构的同时,可以让外部客户代码透明地访问其中包含的元素;同时这种“透明遍历”也为“同一种算法在多种集合对象上进行操作”提供了可能。使用面向对象技术将这种遍历机制抽象为“迭代器对象”为“应对变化中的集合对象”提供了一种优雅的方式。

迭代器模式是一种行为设计模式,简单而言,就是将对集合的遍历有“外部控制”变为“内部控制”,将其封装起来。

数组就是将遍历完全交由外部处理。

Iterator模式的几个要点

  • 迭代抽象:访问一个聚合对象的内容而无需暴露它的内部表示。
  • 迭代多态:为遍历不同的集合结构提供一个统一的接口,从而支持同样的算法在不同的集合结构上进行操作。
  • 迭代器的健壮性考虑:遍历的同时更改迭代器所在的集合结构,会导致问题。(所以 C# 中在 foreach 操作时,不允许更改集合,如果外部有更改,则会报错)。

Part2 foreach 语句的等价形式(while循环)

代码语言:javascript
代码运行次数:0
运行
复制
foreach(var p in Persons){    WriteLine(p);}
// 等价于一个 while 循环IEnumerator<Person> enumerator = persons.GetEnumerator();while(enumerator.MoveNext()){    Person p = enumerator.Current;    WriteLine(p);}
  1. 可以看到,这里并没有调用 Reset 方法,此方法通常用于与 COM 的交互操作,许多 .NET 枚举器抛出 NotSupportedException;
  2. 集合可以被 foreach, 不一定需要实现 IEnumerable 接口,有 GetEnumerator 方法即可。
  3. 一个集合类可以提供多个不同的 GetEnumerator 实现,如 GetEnumerator1,GetEnumerator2,返回不同的 IEnumerator,以实现不同的迭代功能。(见下文)

Part3 IEnumerator 与 yield

一个集合类想要支持被迭代,最主要的是构造一个 Enumerator 类,实现 IEnumerator 接口,在 GetEnumerator 方法中返回这个 Enumerator 类。

如此,在 Enumerator 类中,需要维护 Current 属性和 MoveNext 方法,在 MoveNext 方法中,更新 Current 的值,并返回是否还有后续值的 bool 判断。

在实现 IEnumerator 接口时,通常也要实现其泛型版本 IEnumerator{T}。

这段文字看起来有点晕,实际上,实现一个 IEnumerator 也是一个苦力活。在实际的编程中,一般直接使用已有的集合元素,不必从头实现一个 IEnumerator 。

yield 是 C# 提供语法糖,可以方便的实现 IEnumerator 接口。如:

代码语言:javascript
代码运行次数:0
运行
复制
public IEnumerator<string> GetEnumerator(){    yield return "A";    yield return "B";    yield return "C";    // ...    yield return "Z";}

这样,实际上就实现了一个集合,这个集合保存了大写的 26 个字母。

yield return 语句返回集合的一个元素,并移动到下一个元素,相当于同时维护 CurrentMoveNextyield break 可停止迭代。

使用 yield,编译器会创建一个状态机,用于实际维护 CurrentMoveNext

Part4 实现多个不同的 IEnumerator

有一个 MusicCollection 集合类,里面包含了 IList{Music} 集合,现在要在其中实现对 Music.Title , Music.Author , Music.Time 进行遍历的支持,可以这么做:

代码语言:javascript
代码运行次数:0
运行
复制
public class MusicCollection{    private IList<Music> MusicList;    public MusicCollection(IList<Music> musicList)    {        MusicList = musicList;    }
    public IEnumerator<string> GetTitleEnumerator()    {        for(int i=0;i<MusicList.Length;i++)        {            yield return MusicList[i].Title;        }    }
    public IEnumerator<string> GetAuthorEnumerator()    {        for(int i=0;i<MusicList.Length;i++)        {            yield return MusicList[i].Author;        }    }
    public IEnumerator<string> GetTimeEnumerator()    {        for(int i=0;i<MusicList.Length;i++)        {            yield return MusicList[i].Time;        }    }}
// 外部调用:pubic class Test{    public void Test()    {        var musicList = new List<Music>();        var musicCollection = new MusicCollection(musicList);        foreach(string title in musicCollection.GetTitleEnumerator())        {            Console.WriteLine(title);        }    }}

迭代器中还可以返回迭代器(嵌套),有趣的用法。

Part5 线程安全

迭代显然是非线程安全的,每次 IEnumerable 都会生成新的 IEnumerator,从而形成多个互相不影响的迭代过程。

在迭代过程中,不能修改迭代集合,否则不安全。

简述c#中可枚举对象和遍历器的工作原理? - 知乎

线程安全的枚举在C#中_C#_编程语言_或代码

原文链接: https://cloud.tencent.com/developer/article/2481444

本作品采用 「署名 4.0 国际」 许可协议进行许可,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018年7月21日 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Part1 迭代器模式 与 接口
  • Part2 foreach 语句的等价形式(while循环)
  • Part3 IEnumerator 与 yield
  • Part4 实现多个不同的 IEnumerator
  • Part5 线程安全
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档