IEnumerable接口
IEnumerable接口:实现该接口的类,表明该类下有可以枚举的元素
public interface IEnumerable { //返回一个实现了IEnumerator接口的对象 //我的理解:实现该接口的类,用其下哪个可枚举的元素实现该方法那么当该类被枚举时,将得到该类下的某个可枚举元素的元素 IEnumerator GetEnumerator(); }
public class TestEnumerable : IEnumerable { //可枚举元素 private Student[] _students; public TestEnumerable(Student[] list) { _students = list; } public IEnumerator GetEnumerator() { //当TestEnumerator被枚举时,将得到_students的元素 return _students.GetEnumerator(); } } public class Student { public string ID { get; set; } public string Name { get; set; } public Student(string id, string name) { ID = id; Name = name; } }
调用代码:
public static void Main() { Student[] students = new Student[3] { new Student("ID1", "Name1"), new Student("ID2", "Name2"), new Student("ID3", "Name3"), }; TestEnumerable testEnumerable = new TestEnumerable(students); foreach (Student s in testEnumerable) { Console.WriteLine("ID:{0};Name:{1}", s.ID, s.Name); } }
如上文所说, TestEnumerable类用foreach枚举,得到的每一个对象就是上文提到的“可以枚举的元素(Student[])的每一个对象” 。
IEnumerator接口:枚举的真正实现
public interface IEnumerator { //获取集合中的当前元素 object Current{get;set;} //枚举数推进到集合的下一个元素,当存在下一个对象时返回true,不存在时返回false bool MoveNext(); //枚举数设置为其初始位置,该位置位于集合中第一个元素之前 void Reset(); }
public class TestEnumerator : IEnumerator { //指针,指向枚举元素 (为什么这里是-1,因为在MoveNext()中,_position++) private int _position = -1; private Student[] _students; public TestEnumerator() { } public TestEnumerator(Student[] list) { _students = list; } /// <summary> /// 获取当前元素 /// </summary> public object Current { get { return _students[_position]; } } /// <summary> /// 移动到下一个元素 /// </summary> /// <returns>成功移动到下一个元素返回:true;失败返回:false</returns> public bool MoveNext() { _position++; return _position < _students.Length; } /// <summary> /// 重置到第一个元素 /// </summary> public void Reset() { _position = -1; } } public class TestEnumerable2 : IEnumerable { private Student[] _students; public TestEnumerable2(Student[] list) { _students = list; } public IEnumerator GetEnumerator() { return new TestEnumerator(_students); } }
调用代码:
public static void Main() { Student[] students = new Student[3] { new Student("ID1", "Name1"), new Student("ID2", "Name2"), new Student("ID3", "Name3"), }; TestEnumerable2 testEnumerator = new TestEnumerable2(students); foreach (Student s in testEnumerator) { Console.WriteLine("ID:{0};Name:{1}", s.ID, s.Name); } }
IEnumerable接口与IEnumerator接口算介绍完毕了。
大家看IEnumerator接口的实现,就是实现一个属性,两个调整枚举点的方法。如果能有一种方式,能自动帮我们完成IEnumerator接口的实现,那么将方便很多。
在.NET Framework 3.5中有了yield,这个关键字可以帮我们自动生成实现IEnumerator的类。
yield
yield关键字,可以帮我们自动生成一个实现了IEnumerator的类,来完成枚举。
下面,我们先来看一下MSDN上的介绍
1.foreach 循环的每次迭代都会调用迭代器方法
2.迭代器方法运行到 yield return
语句时,会返回一个expression表达式并保留当前在代码中的位置
3.当下次调用迭代器函数时执行从该位置重新启动
其实msdn这里解释的有点啰嗦了。简单点说,就是迭代器运行到yield return语句时,将自动做两件事。
1.记录当前访问的位置
2.当下次调用迭代器函数时执行从该位置重新启动:MoveNext()
下面我们来看一段实例代码:
public class TestEnumerable3 : IEnumerable { private Student[] _students; public TestEnumerable3(Student[] list) { _students = list; } //实现与之前有不同 public IEnumerator GetEnumerator() { //迭代集合 foreach (Student stu in _students) { //返回当前元素,剩下的就交给yield了,它会帮我们生成一个实现了接口IEnumerator的类 //来帮我们记住当前访问到哪个元素。 yield return stu; } } }
下面我贴出一段GetEnumerator()方法的IL代码,来证明yield return 帮我们自动生成了一个实现了IEnumerator接口的类
.method public final hidebysig newslot virtual instance class [mscorlib]System.Collections.IEnumerator GetEnumerator () cil managed { // Method begins at RVA 0x228c // Code size 20 (0x14) .maxstack 2 .locals init ( [0] class TestBlog.TestEnumerable3/'<GetEnumerator>d__0', //这句最重要,初始化了一个实现System.Collections.IEnumerator的class,存储在索引1的位置 [1] class [mscorlib]System.Collections.IEnumerator ) IL_0000: ldc.i4.0 IL_0001: newobj instance void TestBlog.TestEnumerable3/'<GetEnumerator>d__0'::.ctor(int32) IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: ldarg.0 IL_0009: stfld class TestBlog.TestEnumerable3 TestBlog.TestEnumerable3/'<GetEnumerator>d__0'::'<>4__this' IL_000e: ldloc.0 IL_000f: stloc.1 IL_0010: br.s IL_0012 IL_0012: ldloc.1 IL_0013: ret } // end of method TestEnumerable3::GetEnumerator
最后就是调用代码:
static void Main(string[] args) { Student[] students = new Student[3] { new Student("ID1", "Name1"), new Student("ID2", "Name2"), new Student("ID3", "Name3"), }; TestEnumerable3 testEnumerable3 = new TestEnumerable3(students); foreach (Student s in testEnumerable3) { Console.WriteLine("ID:{0};Name:{1}", s.ID, s.Name); } }
IEnumerable构建迭代方法
/// <summary> /// IEnumerable构建迭代方法 /// <param name="_students">排序集合</param> /// <param name="direction">排序顺序(true:顺序;false:倒序)</param> public IEnumerable SortStudents(Student[] _students,bool direction) { if (direction) { foreach (Student stu in _students) { yield return stu; } } else { for (int i = _students.Length; i != 0; i--) { yield return _students[i - 1]; } } }
细心的朋友一定发现了。这个方法的返回类型为IEnumerable,而不像实现接口IEnumerable的GetEnumerator()方法返回类型是IEnumerator。这就是IEnumerable构建迭代方法需要注意的地方。
下面是测试代码:
static void Main(string[] args) { Student[] students = new Student[3] { new Student("ID1", "Name1"), new Student("ID2", "Name2"), new Student("ID3", "Name3"), };
foreach (Student s in SortStudents(students,false)) { Console.WriteLine("ID:{0};Name:{1}", s.ID, s.Name); } }
感谢大家的耐心阅读。