C#中foreach语句深入研究
1、概述
本文通过手动实现迭代器来了解foreach语句的本质。
2、使用foreach语句遍历集合
在c#中,使用foreach语句来遍历集合。foreach语句是微软提供的语法糖,使用它可以简化c#内置迭代器的使用复杂性。编译foreach语句,会生成调用getenumerator和movenext方法以及current属性的代码,这些方法和属性恰是c#内置迭代器所提供的。下面将通过实例来说明这一切。
例1:使用foreach来遍历集合
//************************************************************ // // foreach应用示例代码 // // author:三五月儿 // // date:2014/09/10 // // //************************************************************ using system; using system.collections; using system.collections.generic; namespace ienumerableexp { class program { static void main(string[] args) { list<student> studentlist = new list<student>() { new student(){id = 1, name = "三五月儿", age = 23}, new student(){id = 2, name = "张三丰", age = 108}, new student(){id = 3, name = "艾尔克森", age = 25}, new student(){id = 3, name = "穆里奇", age = 27} }; foreach (var student in studentlist) { console.writeline("id = {0}, name = {1}, age = {2}", student.id,student.name,student.age); } } } public class student { public int id { get; set; } public string name { get; set; } public int age { get; set; } } }
代码中,使用foreach语句遍历student对象的集合,依次输出student对象的id,name,age属性值。使用ildasm查看程序对应的il代码,下面这些是与foreach语句相关的il代码:
il_00c6: callvirt instance valuetype [mscorlib]system.collections.generic.list`1/enumerator<!0> class [mscorlib]system.collections.generic.list`1<class ienumerableexp.student>::getenumerator() il_00d1: call instance !0 valuetype [mscorlib]system.collections.generic.list`1/enumerator<class ienumerableexp.student>::get_current() il_0102: call instance bool valuetype [mscorlib]system.collections.generic.list`1/enumerator<class ienumerableexp.student>::movenext()
在il代码中,是不是找到了getenumerator和movenext方法以及current属性的身影,可见:foreach语句确实是微软提供的用来支持c#内置迭代器操作的语法糖,因为这些方法和属性正是c#内置迭代器所提供的。
当然,除了使用foreach语句来遍历集合外,还可以使用c#内置迭代器提供的方法和属性来遍历集合,本例中还可以使用下面的代码来完成遍历操作:
ienumerator<student> studentenumerator = studentlist.getenumerator(); while (studentenumerator.movenext()) { var currentstudent = studentenumerator.current as student; console.writeline("id = {0}, name = {1}, age = {2}", currentstudent.id, currentstudent.name, currentstudent.age); }
在第二种方法中,通过调用getenumerator和movenext方法以及current属性来完成遍历操作,是不是与foreach语句编译后生成的代码一致啊。
两种遍历方法,都会得到下图所示结果:
图1 遍历集合元素
查看代码中getenumerator和movenext方法以及current属性的定义,发现getenumerator方法来自于ienumerable接口,而movenext方法与current属性来自于ienumerator接口。实现c#迭代器都应该实现这两个接口。下面就手动实现一个迭代器来操作学生对象的集合。
3、手动实现一个迭代器
前面使用到的是c#内置迭代器,当然,我们完全可以手动实现一个自己的迭代器。
例2:手动实现迭代器
//************************************************************ // // foreach应用示例代码 // // author:三五月儿 // // date:2014/09/10 // // //************************************************************ using system; using system.collections; using system.collections.generic; namespace ienumerableexp { class program { static void main(string[] args) { student[] students = new student[4] { new student(){id = 1, name = "三五月儿", age = 23}, new student(){id = 2, name = "张三丰", age = 108}, new student(){id = 3, name = "艾尔克森", age = 25}, new student(){id = 3, name = "穆里奇", age = 27} }; studentset studentset = new studentset(students); foreach (var student in studentset) { console.writeline("id = {0}, name = {1}, age = {2}", student.id, student.name, student.age); } } } public class student { public int id { get; set; } public string name { get; set; } public int age { get; set; } } public class studentset : ienumerable { private student[] students; public studentset(student[] inputstudents) { students = new student[inputstudents.length]; for (int i = 0; i < inputstudents.length; i++) { students[i] = inputstudents[i]; } } ienumerator ienumerable.getenumerator() { return (ienumerator)getenumerator(); } public studentenumerator getenumerator() { return new studentenumerator(students); } } public class studentenumerator : ienumerator { public student[] students; int position = -1; public studentenumerator(student[] students) { this.students = students; } public bool movenext() { position++; return (position < students.length); } public void reset() { position = -1; } object ienumerator.current { get { return current; } } public student current { get { try { return students[position]; } catch (indexoutofrangeexception) { throw new invalidoperationexception(); } } } } }
代码中定义学生集合类studentset,在类中使用student类型的数组来保存学生元素,该类实现ienumerable接口,所以studentset类必须实现ienumerable接口的getenumerator方法,该方法返回实现了ienumerator接口的迭代器studentenumerator。
下面来看看studentenumerator类的定义,studentenumerator表示遍历学生集合的迭代器,使用它提供的方法和属性可以遍历集合的元素,该类实现ienumerator接口,所以必须实现ienumerator接口提供的movenext和reset方法以及current属性。studentenumerator类使用student类型的集合students来保存需要遍历的集合。使用私有变量position来记录元素的位置,一开始position被赋值为-1,定位于集合中第一个元素的前面,在reset方法中也可以将position的值置为-1,表示回到遍历操作前的状态。在movenext方法中先将position加1,再将其与集合的长度进行比较,看是否已经遍历完了所有元素,若未完返回true,否则返回false。在只读属性current的实现中通过代码students[position]返回students集合中position位置的元素值。在使用迭代器时,需要先调用movenext方法判断下一个元素是否存在,如存在使用current属性得到这个值,若不存在则表示已经遍历完所有元素,将停止遍历操作。
代码中同样使用foreach语句来遍历studentset对象中的元素并输出,与使用内置迭代器的效果一致。
4、总结
实现迭代器需要借助于ienumerable与ienumerator接口,接口ienumerator提供的方法getenumerator可以返回实现ienumerator接口的迭代器,而ienumerator接口中包含了实现迭代器所需的方法及属性的定义。凡是实现了迭代器的类都可以使用foreach语句来遍历其元素,因为foreach语句是微软提供的支持内置迭代器的语法糖,编译foreach语句后生成的代码与使用迭代器的代码完全一致。