首页 > 编程开发 > Objective-C编程 >
-
关于.NET中迭代器的实现以及集合扩展方法的理解
在C#中所有的数据结构类型都实现IEnumerable或IEnumerable<T>接口(实现迭代器模式),可以实现对集合遍历(集合元素顺序访问)。换句话可以这么说,只要实现上面这两个接口的类都是集合类,都能够进行遍历。工作中用过很多扩展方法对泛型集合(IEnumerable<T>)元素进行处理,一直很想一探究竟,通过参考一些资料(主要是大话设计模式和C#从现象到本质这两本书),总结下自己对迭代器和扩展方法的一些理解:
一、自己实现一个迭代器。
.NET平台已经提供了IEnumerable和IEumerator及泛型版本接口,不需要再建了,实现他们就好。
-
建一个集合类要实现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 }
-
建迭代器类(升序、倒序)要实现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:第一次开始写博客,希望以后坚持下去,将博客作为笔记,记录工作或学习中自己的一些的总结和理解,能够看到自己一点一点的成长下去,加油!
文章出处:https://www.cnblogs.com/jn-shao/p/13976340.html