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

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

一、自己实现一个迭代器。
  .NET平台已经提供了IEnumerable和IEumerator及泛型版本接口,不需要再建了,实现他们就好。
  1. 建一个集合类要实现IEnumerable接口,实现接口方法GetEnumerator()返回一种迭代器对象,并将集合关联(引用)到迭代器中,这里集合类不直接使用List,因为它已经实现了迭代器,而是自己实现一个能够迭代的集合类。代码:

 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接口,这里在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对象集合便有了迭代功能,可以实现对集合元素的顺序访问(遍历)。建一个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         }

  运行结果:

在这里插入图片描述

  这里:

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类),可以用(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接口方法的方式都是一样的。

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值