在C#中所有的数据结构类型都实现IEnumerable或IEnumerable<T>接口(实现迭代器模式),可以实现对集合遍历(集合元素顺序访问)。换句话可以这么说,只要实现上面这两个接口的类都是集合类,都能够进行遍历。工作中用过很多扩展方法对泛型集合(IEnumerable<T>)元素进行处理,一直很想一探究竟,通过参考一些资料(主要是大话设计模式和C#从现象到本质这两本书),总结下自己对迭代器和扩展方法的一些理解:

一、自己实现一个迭代器。

  .NET平台已经提供了IEnumerable和IEumerator及泛型版本接口,不需要再建了,实现他们就好。

  1. 建一个集合类要实现IEnumerable<T>接口,实现接口方法GetEnumerator()返回一种迭代器对象,并将集合关联(引用)到迭代器中,这里集合类不直接使用List<T>,因为它已经实现了迭代器,而是自己实现一个能够迭代的集合类。代码:

     1     /// <summary>
    2 /// 泛型集合类
    3 /// </summary>
    4 public class MyAggregate<T> : IEnumerable<T>
    5 {
    6 private IList<T> list = new List<T>();
    7 public int Count
    8 {
    9 get { return list.Count; }
    10 }
    11 public T this[int count]
    12 {
    13 get { return list[count]; }
    14 set { list.Insert(count, value); }
    15 }
    16 public void Add(T item)
    17 {
    18 list.Add(item);
    19 }
    20 public void Remove(T item)
    21 {
    22 list.Remove(item);
    23 }
    24 public IEnumerator<T> GetEnumerator()
    25 {
    26 return new MyEnumerator<T>(this,0); //MyEnumerator<T>为升序迭代器
    27 }
    28 IEnumerator IEnumerable.GetEnumerator() => new MyEnumerator<T>(this,0);
    29 }
  2. 建迭代器类(升序、倒序)要实现IEnumerator<T>接口,这里在MoveNext()方法中加入了状态管理(switch case代码部分)。代码:

     1     /// <summary>
    2 /// 升序迭代器
    3 /// </summary>
    4 public class MyEnumerator<T> : IEnumerator<T>
    5 {
    6 private MyAggregate<T> _aggregate;//遍历的集合
    7 private T _current;//当前集合元素,越界停留最后一个元素
    8 private int _position = -1;//当前位置
    9 private int _state;//当前状态,标识迭代时MoveNext()从哪里恢复执行
    10 public MyEnumerator(MyAggregate<T> aggregate,int state)
    11 {
    12 this._aggregate = aggregate;
    13 this._state = state;
    14 }
    15 //只读
    16 public T Current => this._current;
    17 object IEnumerator.Current => this._current;
    18 //无越界返回true并将集合元素赋值_current,越界返回false
    19 public bool MoveNext()
    20 {
    21 switch (this._state)
    22 {
    23 case 0://调用未执行状态
    24 this._state = -1;//运行状态
    25 this._position = 0;
    26 break;
    27 case 1://标识下次恢复执行状态
    28 this._state = -1;
    29 this._position++;
    30 break;
    31 default://-1越界
    32 return false;
    33 }
    34 bool result;
    35 if (this._position < this._aggregate.Count)
    36 {
    37 this._current = this._aggregate[_position];
    38 this._state = 1;
    39 result = true;
    40 return result;
    41 }
    42 result = false;
    43 return result;
    44 }
    45 public void Reset()
    46 {
    47 this._state = 0;//恢复迭代器调用未执行状态(MoveNext)
    48 }
    49 public void Dispose() { }
    50 }

  这时候,迭代器已经实现了,MyAggregate<T>对象集合便有了迭代功能,可以实现对集合元素的顺序访问(遍历)。建一个Person类,测试一下。代码:

 1     public class Person
2 {
3 public Person(string name,int age) {
4 this.Name = name;
5 this.Age = age;
6 }
7 public string Name { get; set; }
8 public int Age { get; set; }
9
10 public override string ToString()
11 {
12 return string.Format("我叫{0},今年{1}岁", this.Name, this.Age);
13 }
14 }

  主函数代码:

 1         static void Main(string[] args)
2 {
3 MyAggregate<Person> aggregate = new MyAggregate<Person>();//处理有原始容器集合,元素会改变
4 aggregate[0] = new Person("张三", 15);
5 aggregate[1] = new Person("李四", 16);
6 aggregate.Add(new Person("王二", 17));
7 aggregate.Add(new Person("麻子", 18));
8
9 //集合类实现IEnumerable<T> 接口,将集合类交给具体迭代器 迭代处理
10 var iterator = aggregate.GetEnumerator();
11 while (iterator.MoveNext())
12 {
13 Console.WriteLine(iterator.Current);
14 }
15 foreach (var item in aggregate)
16 {
17 item.Age = 20;//item即aggregate.Current只读,迭代元素值不能更改(不需编译直接就报错),可以改元素属性
18 Console.WriteLine(item);
19 }
20 foreach (var item in aggregate)
21 {
22 Console.WriteLine(item);
23 }
24 Console.ReadKey();
25 }
26

  运行结果:

这里:

1 foreach (var item in aggregate)
2 {
3 Console.WriteLine(item);
4 }

由编译器生成IL,本质其实就是下面这几行代码,两个作用一样,只是foreach语句可读性更强。

1 //foreach语句本质
2 var iterator = aggregate.GetEnumerator();
3 while (iterator.MoveNext())
4 {
5 var item_1 = iterator.Current;//C# 5之后,针对foreach每次循环都会引入新的迭代变量赋值,避免委托调用时捕获变量都是迭代后的当前值
6 Console.WriteLine(item_1);
7 }

  原始迭代器实现方式比较麻烦,代码要多建一个附加类(上面的MyEnumerator<T>类),可以用(C# 2之后)提供的yield return 关键字进行简化,将上面集合类中的GetEnumerator()方法改写成:

1         public IEnumerator<T> GetEnumerator()
2 {
3 for (int count = 0; count < this.Count; count++)
4 {
5 yield return this[count];
6 }
7 }
8

  这样不需要再创建附加类,就可以达到上面代码一样的效果,能够用关键字简化编译器自动生成等效的IL代码,原因是实现IEnumerator<T>接口方法的方式都是一样的。

 

  注:yield return只能用在返回值为IEnumerable和IEnumerator类型的方法中,会将MoveNext()变成状态判断的状态机,所谓的延迟加载(取值才真正运行有yield的方法)其实本质就是调用GetEnumerator方法只是获取一个迭代器对象(将集合数据关联到该对象中,迭代遍历取值完全交给迭代器对象进行处理),不取值是不会运行IEnumerator(迭代器对象)中MoveNext方法的(每个yield return之前的代码都会被编译到一个状态中进行处理),状态一直为0,所以可以通过后面加ToList()等方法遍历取值才能真正运行该方法。

二、自己实现简易的扩展方法静态类,实现类似的功能。

  值得注意的是在foreach语句中除了集合元素值不能更改还要求不能为集合删除或新增元素(因为会导致状态/索引错乱),那么需要集合处理时则进行linq操作。工作中总会避免不了用到一些扩展方法(linq的基础,都是通过扩展方法实现的)处理泛型集合,常用的有Where()、Select()、SelectMany()、FirstOrDefault()、LastOrDefault()、OrderBy()、OrderByDescending()、GroupBy()等等,感觉功能非常强大使用方便,于是根据用法简单模仿实现一下。代码:

 1     public static class MyEnumerable
2 {
3 //public static IEnumerable<TSource> MyWhere<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
4 //{
5 // MyAggregate<TSource> aggregate = new MyAggregate<TSource>();
6 // foreach (TSource ts in source)
7 // {
8 // if (predicate(ts))
9 // {
10 // aggregate.Add(ts);
11 // }
12 // }
13 // return aggregate;
14 //}
15 //延迟执行,yield return 用在返回值为IEnumerable和IEnumerator中
16 public static IEnumerable<TSource> MyWhere<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
17 {
18 foreach (TSource ts in source)
19 {
20 if (predicate(ts))
21 {
22 yield return ts;//将值取出放入到另一个状态机中处理,原集合成员是不变的
23 }
24 }
25 }
26
27 //public static IEnumerable<TResult> MySelect<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
28 //{
29 // MyAggregate<TResult> aggregate = new MyAggregate<TResult>();
30 // foreach (TSource ts in source)
31 // {
32 // aggregate.Add(selector(ts));
33 // }
34 // return aggregate;
35 //}
36 public static IEnumerable<TResult> MySelect<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
37 {
38 foreach (TSource ts in source)
39 {
40 yield return selector(ts);
41 }
42 }
43 }

  主函数代码:

 1         static void Main(string[] args)
2 {
3 MyAggregate<Person> aggregate = new MyAggregate<Person>();//处理有原始容器集合,元素会改变
4 aggregate[0] = new Person("张三", 15);
5 aggregate[1] = new Person("李四", 16);
6 aggregate.Add(new Person("王二", 17));
7 aggregate.Add(new Person("麻子", 18));
8
9 IEnumerable<Person> people = aggregate.MyWhere(p => p.Age > 16).ToList();
10 foreach (var person in people)
11 {
12 Console.WriteLine(person);
13 }
14
15 IEnumerable<string> strs = aggregate.MySelect(p => { if (p.Age < 18) return "我叫" + p.Name + ",未成年"; else return "我叫" + p.Name + ",已成年"; });
16
17 IEnumerable<string> strs1 = aggregate.MyWhere(p => p.Age > 16).MySelect(m => { if (m.Age < 18) return "我叫" + m.Name + ",未成年"; else return "我叫" + m.Name + ",已成年"; });
18 foreach (var str in strs)
19 {
20 Console.WriteLine(str);
21 }
22 foreach (var str in strs1)
23 {
24 Console.WriteLine(str);
25 }
26 Console.ReadKey();
27 }

  运行结果:

  

PS:第一次开始写博客,希望以后坚持下去,将博客作为笔记,记录工作或学习中自己的一些的总结和理解,能够看到自己一点一点的成长下去,加油!

关于.NET中迭代器的实现以及集合扩展方法的理解的更多相关文章

  1. Android中的广播基本实现及回调方法的理解

    在Android中broadcast这一节的内容其实不算多主要是牵扯到一个broadcastreceiver类,这个类是一个抽象类,下面有一个抽象方法onreceiver(),可以再我们收到网络状态变 ...

  2. jquery中dom元素的attr和prop方法的理解

    一.背景 在编写使用高版本[ jQuery 1.6 开始新增了一个方法 prop()]的jquery插件进行编写js代码的时候,经常不知道dom元素的attr和prop方法到底有什么区别?各自有什么应 ...

  3. ASP.NET MVC验证框架中关于属性标记的通用扩展方法

    http://www.cnblogs.com/wlb/archive/2009/12/01/1614209.html 之前写过一篇文章<ASP.NET MVC中的验证>,唯一的遗憾就是在使 ...

  4. 关于在C#中对类中的隐藏基类方法和重写方法的理解

    最近在学习C#,在C#中的类看到重写和隐藏基类的方法这些概念.才开始感觉自己不是很理解这些概念.也区分不开这些概念.通过自己的查找资料和练习后.慢慢的理解了类中的隐藏和重写这个概念.在C#中只有在基类 ...

  5. Linq中带有迭代索引的Select扩展方法,为啥知道的人不多呢?

    一:背景 昨天在看C#函数式编程这本书的时候,有一处让我干着急,需求是这样: 给多行文字加上数字列表项. 针对这个需求你会如何快捷高效的给每个项目加上数字编号呢? 我看书中是这样实现的,如下代码 pu ...

  6. C#中的反射和扩展方法的运用

    前段时间做了一个练手的小项目,采用的是三层架构,也就是Models,IDAL,DAL,BLL 和 Web , 在DAL层中各个类中有一个方法比较常用,那就是 RowToClass ,顾名思义,也就是将 ...

  7. ASP.NET + MVC5 入门完整教程四---MVC 中使用扩展方法

    https://blog.csdn.net/qq_21419015/article/details/80433640 1.示例项目准备1)项目创建新建一个项目,命名为LanguageFeatures ...

  8. 解决.net中截取字符串的汉字与数字还有静态扩展方法

      转载 http://blog.163.com/cn_dreamgo/blog/static/52679452200961033212407/ 这两天在C#编程中应用到C#代码与C的代码信息交互,但 ...

  9. Java集合中迭代器的常用用法

    该例子展示了一个Java集合中迭代器的常用用法public class LinkedListTest { public static void main(String[] args) { List&l ...

随机推荐

  1. C# 9 新特性 —— 补充篇

    C# 9 新特性 -- 补充篇 Intro 前面我们分别介绍了一些 C# 9 中的新特性,还有一些我觉得需要了解一下的新特性,写一篇作为补充. Top-Level Statements 在以往的代码里 ...

  2. Hive表的基本操作

    目录 1. 创建表 2. 拷贝表 3. 查看表结构 4. 删除表 5. 修改表 5.1 表重命名 5.2 增.修.删分区 5.3 修改列信息 5.4 增加列 5.5 删除列 5.6 修改表的属性 1. ...

  3. Educational Codeforces Round 102 (Rated for Div. 2)

    比赛地址 A(水题) 题目链接 题目: 给出一个数组\(a\)并能进行一个操作使得数组元素更改为数组任意其他两元素之和,问是否可以让数组元素全部小于等于\(d\) 解析: 排序后判断最大值是否小于等于 ...

  4. 搭乘“AI大数据”快车,肌肤管家,助力美业数字化发展

    经过疫情的发酵,加速推动各行各业进入数据时代的步伐.美业,一个通过自身技术.产品让用户变美的行业,在AI大数据的加持下表现尤为突出. 对于美妆护肤企业来说,一边是进入存量市场,一边是疫后的复苏期,一边 ...

  5. CopyOnWriteArrayList设计思路与源码分析

    CopyOnWriteArrayList实现了List接口,RandomAccess,Cloneable,Serializable接口. CopyOnWriteArrayList特性 1.线程安全,在 ...

  6. LRU缓存的实现

    文章目录 LRU简介 LRU算法分析 实现代码 节点类 双向链表 LRUCache类 测试类 总结 LRU简介 LRU是"Least Recently Used"的简写,意思是最近 ...

  7. nginx日志详细说明

    Nginx日志主要分为两种:访问日志和错误日志.日志开关在Nginx配置文件(/etc/nginx/nginx.conf)中设置,两种日志都可以选择性关闭,默认都是打开的. 访问日志 访问日志主要记录 ...

  8. 前端知识(二)03-Webpack-谷粒学院

    目录 一.什么是Webpack 二.Webpack安装 1.全局安装 2.安装后查看版本号 三.创建项目 1.初始化项目 2.创建src文件夹 3.src下创建common.js 4.src下创建ut ...

  9. Python数据模型与Python对象模型

    数据模型==对象模型 Python官方文档说法是"Python数据模型",大多数Python书籍作者说法是"Python对象模型",它们是一个意思,表示&quo ...

  10. 树莓派安装 Ubuntu 20.04 LTS 碰壁指南

    树莓派安装 Ubuntu 20.04 LTS 碰壁指南 设备 Raspberry 4B 4+32G 系统 Ubuntu 20.04 LTS 1.镜像下载与烧录 镜像下载地址:https://cdima ...