平时工作中我们经常用foreach来迭代一个集合。比如
1 foreach (Student student in myClass) 2 { 3 Console.WriteLine(student); 4 } 5
基本所有的集合都能够foreach,但是必须要实现IEnumerable接口。IEnumerable接口很简单,就只有一个IEnumerator GetEnumerator() 方法。看这个方法的定义就知道,仅仅是公开了另一个接口IEnumerator。而IEnumerator才是真正的支持一个集合的迭代。IEnumerator有1个属性和2个方法。
public object Current;
public void Reset();
public bool MoveNext();
只允许读取集合的数据,而不允许修改。为了详细的讲解,我们来写一个简单的例子,就会一目了然。
首先我们创建一个学生类Student如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace IenumerableDemo 7 { 8 public class Student 9 { 10 #region 私有变量 11 12 private readonly string _id; 13 private string _firstname; 14 private string _lastname; 15 16 #endregion 17 #region 属性 18 public string ID { get { return _id; } } 19 20 public string FirstName { get { return _firstname; } set { _firstname = value; } } 21 22 public string LastName { get { return _lastname; } set { _lastname = value; } } 23 24 25 #endregion 26 27 #region 构造函数 28 29 public Student(string id, string firstname, string lastname) 30 { 31 this._id = id; 32 33 this._firstname = firstname; 34 35 this._lastname = lastname; 36 } 37 38 #endregion 39 #region 重写基类object方法 40 41 public override string ToString() 42 { 43 return string.Format("{0} {1},ID:{2}", _firstname, _lastname, _id); 44 } 45 46 public override bool Equals(object obj) 47 { 48 if (obj == null) return false; 49 if (Object.ReferenceEquals(this, obj)) return true; 50 if (this.GetType() != obj.GetType()) return false; 51 52 Student objstudent = (Student)obj; 53 if (_id.Equals(objstudent._id)) return true; 54 55 return false; 56 } 57 58 public override int GetHashCode() 59 { 60 return _id.GetHashCode(); 61 } 62 #endregion 63 64 } 65 } 66 67
接下来我们定义一个ClassList类来承载学生。让我们先忘记Ienmerable。这个类包含一个ArrayList字段_student,在构造函数中模拟3个学生。_student是私有的,不对外公开的。
1 using System; 2 using System.Collections; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Text; 6 7 namespace IenumerableDemo 8 { 9 public class ClassList 10 { 11 12 #region private Members 13 14 private readonly string _id; 15 16 private ArrayList _students; 17 18 #endregion 19 20 #region Properties 21 public string ID { get { return _id; } } 22 #endregion 23 24 #region Constructors 25 26 public ClassList(string id) 27 { 28 29 this._id = id; 30 _students = new ArrayList() { new Student("12345", "John", "Smith"), new Student("09876", "Jane", "Doe"), new Student("76403", "Bob", "Johnson") }; 31 32 } 33 34 #endregion 35 36 37 } 38 }
为了让对象支持foreach 迭代,ClassList类需要实现IEnumerable。因为我们的student是存在ArrayList对象里的,而ArrayList类已经实现了IEnumerable,我们就可以使用ArrayList类的Ienumerable。
1 public IEnumerator GetEnumerator() 2 { 3 4 return (_students as IEnumerable).GetEnumerator(); 5 6 }
最终的代码贴一下:
1 using System; 2 using System.Collections; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Text; 6 7 namespace IenumerableDemo 8 { 9 public class ClassList:IEnumerable 10 { 11 12 #region private Members 13 14 private readonly string _id; 15 16 private ArrayList _students; 17 18 #endregion 19 20 #region Properties 21 public string ID { get { return _id; } } 22 #endregion 23 24 #region Constructors 25 26 public ClassList(string id) 27 { 28 29 this._id = id; 30 _students = new ArrayList() { new Student("12345", "John", "Smith"), new Student("09876", "Jane", "Doe"), new Student("76403", "Bob", "Johnson") }; 31 32 } 33 34 #endregion 35 36 public IEnumerator GetEnumerator() 37 { 38 39 return (_students as IEnumerable).GetEnumerator(); 40 41 } 42 } 43 }
然后我们调用看看使用ArrayList的Ienumerable效果:
1 using System; 2 using System.Collections; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Text; 6 7 namespace IenumerableDemo 8 { 9 class Program 10 { 11 static void Main(string[] args) 12 { 13 ClassList myClass = new ClassList("History 204"); 14 15 16 foreach (Student student in myClass) 17 18 Console.WriteLine(student); 19 20 21 Console.ReadLine(); 22 } 23 24 25 } 26 27 28 }
看来还是实现了效果。那么接下来我们看看自定义实现IEnumerable。实现IEnumerable其实只要实现IEnumerator接口就可以了。
我们创建我们自己的一个自定义类ClassEnumerator 来实现IEnumerator来完成和上面相同的结果。这个类基本上就只是通过_students的索引来进行迭代,Reset()方法就是把索引设置为-1.Current属性来获取当前的student,MoveNext()来跳到Current的下一个数据,并返回一个boolean来表示是否到了集合最后。
1 using System; 2 using System.Collections; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Text; 6 7 namespace IenumerableDemo 8 { 9 public class ClassEnumerator : IEnumerator 10 { 11 12 private ClassList _classList; 13 14 private int _index; 15 16 public ClassEnumerator(ClassList classList) 17 { 18 this._classList = classList; 19 20 _index = -1; 21 } 22 23 #region IEnumerator Members 24 25 public void Reset() 26 { 27 this._index = -1; 28 } 29 30 public object Current 31 { 32 get { return _classList.Students[_index]; } 33 } 34 35 public bool MoveNext() 36 { 37 _index++; 38 if (_index >= _classList.Students.Count) 39 return false; 40 else 41 return true; 42 43 } 44 #endregion 45 46 } 47 }
最后修改我们的ClassLst类:
1 using System; 2 using System.Collections; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Text; 6 7 namespace IenumerableDemo 8 { 9 public class ClassList : IEnumerable 10 { 11 12 #region private Members 13 14 private readonly string _id; 15 16 private ArrayList _students; 17 18 #endregion 19 20 #region Properties 21 public string ID { get { return _id; } } 22 23 public ArrayList Students { get { return _students; } } 24 #endregion 25 26 #region Constructors 27 28 public ClassList(string id) 29 { 30 31 this._id = id; 32 _students = new ArrayList() { new Student("12345", "John", "Smith"), new Student("09876", "Jane", "Doe"), new Student("76403", "Bob", "Johnson") }; 33 34 } 35 36 #endregion 37 38 public IEnumerator GetEnumerator() 39 { 40 41 return (IEnumerator)new ClassEnumerator(this); 42 43 } 44 } 45 }
可已看到还是相当简单的。运行结果和上面是一样的。
下来看看 foreach怎么工作。
其实foreach只是语法糖,最终会被CLR翻译成
1 IEnumerator enumerator = myClass.GetEnumerator(); 2 while (enumerator.MoveNext()) 3 { 4 Console.WriteLine((Student)enumerator.Current); 5 6 }
我们可以把foreach 换成这样试一下,结果是一样滴。
1 using System; 2 using System.Collections; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Text; 6 7 namespace IenumerableDemo 8 { 9 class Program 10 { 11 static void Main(string[] args) 12 { 13 ClassList myClass = new ClassList("History 204"); 14 15 16 //foreach (Student student in myClass) 17 18 // Console.WriteLine(student); 19 20 21 IEnumerator enumerator = myClass.GetEnumerator(); 22 while (enumerator.MoveNext()) 23 { 24 Console.WriteLine((Student)enumerator.Current); 25 26 } 27 28 Console.ReadLine(); 29 } 30 31 32 } 33 34 35 }