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

时间:2022-09-07 12:45:00

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

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

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

  1. 建一个集合类要实现IEnumerable<T>接口,实现接口方法GetEnumerator()返回一种迭代器对象,并将集合关联(引用)到迭代器中,这里集合类不直接使用List<T>,因为它已经实现了迭代器,而是自己实现一个能够迭代的集合类。代码:
    关于.NET中迭代器的实现以及集合扩展方法的理解关于.NET中迭代器的实现以及集合扩展方法的理解
     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代码部分)。代码:
    关于.NET中迭代器的实现以及集合扩展方法的理解关于.NET中迭代器的实现以及集合扩展方法的理解
     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类,测试一下。代码:

关于.NET中迭代器的实现以及集合扩展方法的理解关于.NET中迭代器的实现以及集合扩展方法的理解
 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 }

  主函数代码:

关于.NET中迭代器的实现以及集合扩展方法的理解关于.NET中迭代器的实现以及集合扩展方法的理解
 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

  运行结果:

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

这里:

关于.NET中迭代器的实现以及集合扩展方法的理解关于.NET中迭代器的实现以及集合扩展方法的理解
1 foreach (var item in aggregate)
2 {
3 Console.WriteLine(item);
4 }

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

关于.NET中迭代器的实现以及集合扩展方法的理解关于.NET中迭代器的实现以及集合扩展方法的理解
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()方法改写成:

关于.NET中迭代器的实现以及集合扩展方法的理解关于.NET中迭代器的实现以及集合扩展方法的理解
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()等等,感觉功能非常强大使用方便,于是根据用法简单模仿实现一下。代码:

关于.NET中迭代器的实现以及集合扩展方法的理解关于.NET中迭代器的实现以及集合扩展方法的理解
 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 }

  主函数代码:

关于.NET中迭代器的实现以及集合扩展方法的理解关于.NET中迭代器的实现以及集合扩展方法的理解
 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 }

  运行结果:

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

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

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

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

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

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

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

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

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

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

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

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

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

  6. C&num;中的反射和扩展方法的运用

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

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

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

  8. 解决&period;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. java环境变量以及jdk、jre、jvm

    一.jdk,jre,jvm的了解:jdk全称java development kit即java开发工具包,是整个java的核心,包含了java运行环境jre.java工具包和java的基础类库: jr ...

  2. PHP&plus;JQUEY&plus;AJAX实现分页【转】

    HTML CSS #list{width:680px; height:530px; margin:2px auto; position:relative} #list ul li{float:left ...

  3. redis-设置密码

    1.通过配置文件设置密码 找到redis的安装目录中的如下文件 linux:/etc/redis.confwindows(我的windwos免安装版本):E:\redis\redis-2.4.5-wi ...

  4. 为什么arcgis里,鼠标的图标都变成放大镜不能用了

    做作业做到一半,鼠标的图标就只有放大镜了,不管是点箭头还是作图工具都没用,手抓的也没用,只剩下放大镜的功能和图标了,这是怎么一回事啊?种情况我碰到过几次,具体原因不清楚,但是解决方法是有的:把你的数据 ...

  5. MyEclipse中用Maven创建Web项目&lpar;亲测有效&rpar;

    new --> other   1.Wizards: mvaen 2.Maven Project 3.Next   Use Default Workspace Location   1.weba ...

  6. redis持久化探究

    redis支持两种持久化方式,一种是RDB方式,另一种是AOF方式.redis3.0windows版本默认关闭AOF(appendonly no),而开启RDB,当达到一定条件时,redis就会将内存 ...

  7. Zephyr学习专题

    1 前言 本来想学习Zyphyr的Power Management,但是看着看着就被带进去了. 你看功耗,里面的suspend涉及到时间补偿相关的吧,然后就涉及到了Kernel Clocks/Time ...

  8. java Scanner类注意事项

    1,循环或递归调用获取数字时,不能用hasNextInt()判断是否有输入,不然会陷入死循环,应该用hasNext().获取也不能用nextInt(),应用next(),否则也会死循环 例如这段代码, ...

  9. JQ 获取窗体的高度

    alert($(window).height()); //浏览器时下窗口可视区域高度 alert($(document).height()); //浏览器时下窗口文档的高度 alert($(docum ...

  10. OC - runtime - 1