IEnumerable / IEnumerator
首先,IEnumerable / IEnumerator 接口定义如下:
public interface IEnumerable /// 可枚举接口 { IEnumerator GetEnumerator(); } public interface IEnumerator /// 枚举器接口 { object Current { get; } bool MoveNext(); void Reset(); }
注:Current 没有 set 方法,在 foreach 中不能修改元素 var item 的值。
- IEnumerable:声明式的接口,声明实现了该接口的类是可枚举类型;
- IEnumerator:实现式的接口,IEnumerator 对象说明如何实现一个枚举器;
通过继承 IEnumerable / IEnumerator 接口实现自定义类使用 foreach 语句来遍历自身元素。逻辑关系图:
People <-> MyClass 实现 IEnumerable 接口的 GetEnumerator()方法 EnumeratorPeople <-> MyEnumerator 实现 IEnumerator 接口
- 定义Person类
public class Person { private string name; private int age; public Person(string _name, int _age){ this.name = _name, this.age = _age; } public override string ToString(){ return string.Format("Name:{0},Age:{1}.", name, age); } }
数组定义见主函数,以下2种遍历数组的方法等同,因为所有数组的基类都是 System.Array ,System.Array 类实现了 IEnumerable 接口,可以直接通过 GetEnumerator() 方法返回枚举数对象,枚举数对象可以依次返回请求的数组的元素。
// 利用 foreach 遍历数组 foreach (var person in persons) Console.WriteLine(person.ToString()); // 利用 IEnumerable ~ IEnumerator 遍历数组 IEnumerator it = persons.GetEnumerator(); while (it.MoveNext()){ Person obj = (Person)(it.Current); // 需强制类型转换 Console.WriteLine(obj.ToString()); }
- 定义People类 (MyClass)
public class People { private Person[] persons; public People(Person[] _persons){ persons = new Person[_persons.Length]; for (int i = 0; i < _persons.Length; ++i){ persons[i] = _persons[i]; } } }
注意,People 类的 persons 数组是 private 变量,在主测函数中是无法遍历访问的。
方法 1:将 private 更改为 public:
foreach (var person in people.persons) Console.WriteLine(person.ToString());
方法 2:People 类继承 IEnumerable 接口并实现 GetEnumerator() 方法,有 2 种方法:
[-1-]. 利用数组默认实现了 IEnumerable 和 IEnumerator 接口的事实,重新定义 People 类:
public class People : IEnumerable { ... ... public IEnumerator GetEnumerator(){ return persons.GetEnumerator(); // 方法 1 } }
[-2-]. 自定义枚举数类 (EnumeratorPeople 类如下),继承并实现 IEnumerator 接口,重新定义 People 类:
public class People : IEnumerable { ... ... public IEnumerator GetEnumerator(){ return new EnumeratorPeople(persons); // 方法 2 } }
此时,在方法2中自定义类可以使用如下 foreach 语句来遍历自身元素:
foreach (var person in people) Console.WriteLine(person.ToString());
- 定义EnumeratorPeople类 (MyEnumerator)
public class EnumeratorPeople : IEnumerator { private int position = -1; private Person[] persons; public EnumeratorPeople(Person[] _persons){ persons = new Person[_persons.Length]; for (int i = 0; i < _persons.Length; ++i) persons[i] = _persons[i]; } public object Current{ get{ return persons[position]; } } public bool MoveNext(){ ++position; return (position < persons.Length); } public void Reset(){ position = -1; } }
主函数测试代码
class Program{ static void Main(string[] args){ Person[] persons = { new Person("abc",25), new Person("xyz",22), new Person("qwer",12), new Person("pm",20) }; People people = new People(persons); } }
总结
一个类型是否支持foreach遍历,本质上是实现 IEnumerator 接口,2 种方法:
(1)自定义类只要继承 IEnumerable 接口并实现无参 GetEnumerator() 方法即可,最简单;
(2)在(1)基础上,定义 MyEnumerator 类继承并实现 IEnumerator 接口;
扩展
foreach 语句隐式调用集合的无参 GetEnumerator() 方法。其实,不论集合是否有实现 IEnumerable 接口,只要必须提供无参 GetEnumerator() 方法并返回包含 Current 属性和 MoveNext() 方法的 IEnumerator 对象即可,然后编译器会自动去绑定,无需依赖 IEnumerable 和 IEnumerator 接口,从而实现 foreach 对自定义集合类的遍历。
1 public class People 2 { 3 public class EnumeratorPeople 4 { 5 private int position = -1; 6 private Person[] enumPersons; 7 public EnumeratorPeople(Person[] _persons){ 8 enumPersons = new Person[_persons.Length]; 9 for (int i = 0; i < _persons.Length; ++i){ 10 enumPersons[i] = _persons[i]; 11 } 12 } 13 14 public object Current{ 15 get { return enumPersons[position]; } 16 } 17 public bool MoveNext(){ 18 ++position; 19 return ( position < enumPersons.Length ); 20 } 21 public void Reset(){ 22 position = -1; 23 } 24 } 25 26 private Person[] persons; 27 public People(Person[] _persons){ 28 persons = new Person[_persons.Length]; 29 for (int i = 0; i < _persons.Length; ++i){ 30 persons[i] = _persons[i]; 31 } 32 } 33 public EnumeratorPeople GetEnumerator(){ 34 return new EnumeratorPeople(persons); 35 } 36 }
此处,枚举数类声明为嵌套类,或者集成为一个类,也可以分成单独的 2 个类均可。
延伸问题
- for 与 foreach
for 先取全部再遍历,foreach 边遍历边取值;
- Linq to Object 中返回 IEnumerable 类型?
IEnumerable 是延迟加载的。
参考
· 传统遍历与迭代器;
· IEnumerable和IEnumerator 详解;
· 自定义类实现foreach;深入理解 foreach;
IEnumerable<T> / IEnumerator<T>
优缺点对比
· 非泛型:非类型安全,返回object类型的引用、需要再转化为实际类型 (值类型需要装箱和拆箱);
· 泛型:类型安全,直接返回实际类型的引用;
首先,IEnumerable<T> / IEnumerator<T> 接口定义如下:
public interface IEnumerable<out T> : IEnumerable { IEnumerator<T> GetEnumerator(); } public interface IEnumerator<out T> : IEnumerator, IDisposable { T Current { get; } }
其中,接口 IDisposable 定义为:
public interface IDisposable{ void Dispose(); }
逻辑关系图:
People <-> MyClass 实现 IEnumerable<T> 接口的 泛型GetEnumerator()方法
GenEnumPeople <-> MyGenEnumerator 实现 IEnumerator<T> 接口
注:显式实现非泛型版本,在类中实现泛型版本!如下,类 MyGenEnumerator 实现了 IEnumerator<T>,类MyClass 实现了 IEnumerable<T> 接口。
1 public class MyClass : IEnumerable<T> 2 { 3 public IEnumerator<T> GetEnumerator() { } // IEnumerable<T> 版本 4 IEnumerator IEnumerable.GetEnumerator() { } // IEnumerable 版本 5 } 6 7 public class MyGenEnumerator : IEnumerator<T> 8 { 9 public T Current { get; } // IEnumerator<T> 版本 10 public bool MoveNext() { } 11 public void Reset() { } 12 object IEnumerator.Current { get; } // IEnumerator 版本 13 public void Dispose() { } 14 }
· 定义 People 类 (MyClass)
1 public class People : IEnumerable<Person> 2 { 3 private Person[] persons; 4 public People(Person[] _persons){ 5 persons = new Person[_persons.Length]; 6 for (int i = 0; i < _persons.Length; ++i) 7 persons[i] = _persons[i]; 8 } 9 10 public IEnumerator<Person> GetEnumerator(){ 11 return new GenericEnumeratorPeople(persons); 12 } 13 IEnumerator IEnumerable.GetEnumerator(){ // 显式实现 14 return this.GetEnumerator(); 15 } 16 }
· 定义 GenEnumPeople 类 (MyGenEnumerator)
1 public class GenEnumPeople : IEnumerator<Person> 2 { 3 private int position = -1; 4 private Person[] persons; 5 public GenericEnumeratorPeople(Person[] _persons){ 6 persons = new Person[_persons.Length]; 7 for (int i = 0; i < _persons.Length; ++i) 8 persons[i] = _persons[i]; 9 } 10 11 public Person Current{ 12 get { return persons[position]; } 13 } 14 object IEnumerator.Current{ // 显式实现 15 get { return this.Current; } 16 } 17 public bool MoveNext(){ 18 ++position; 19 return (position < persons.Length); 20 } 21 public void Reset() { position = -1; } 22 public void Dispose() { Console.WriteLine("void Dispose()"); } 23 }
其中,IDisposable 接口的学习参见 由 IDisposable 到 GC;
扩展 :泛型委托应用;
迭代器
C#2.0 利用迭代器可以简单实现 GetEnumerator() 函数。迭代器是用于返回相同类型的值的有序集合的一段代码。利用 yield 关键字,实现控制权的传递和循环变量的暂存,使类或结构支持 foreach 迭代,而不必显式实现 IEnumerable 或 IEnumerator 接口,由 JIT 编译器辅助编译成实现了 IEnumerable 或 IEnumerator 接口的对象。yield return 提供了迭代器一个重要功能,即取到一个数据后马上返回该数据,不需要全部数据加载完毕,有效提高遍历效率(延迟加载)。
- yield 关键字用于指定返回的值,yield return 语句依次返回每个元素,yield break 语句终止迭代;
- 到达 yield return 语句时,保存当前位置,下次调用迭代器时直接从当前位置继续执行;
- 迭代器可以用作方法、运算符或get访问器的主体实现,yield 语句不能出现在匿名方法中;
- 迭代器返回类型必须为 IEnumerable、IEnumerator、IEnumerable<T> 或 IEnumerator<T>;
逻辑关系图:
· · 返回 IEnumerator 类型
返回此类型的迭代器通常为默认迭代器。
· People 类
1 public class People 2 { 3 private Person[] persons; 4 public People(Person[] _persons){ 5 persons = new Person[_persons.Length]; 6 for (int i = 0; i < _persons.Length; ++i) 7 persons[i] = _persons[i]; 8 } 9 10 public IEnumerator GetEnumerator(){ 11 return IterMethod1; // [1] 12 return IterMethod2(); // [2] 13 } 14 public IEnumerator IterMethod1 // [1].属性返回 IEnumerator 15 { 16 get { 17 for (int i = 0; i < persons.Length; ++i) 18 yield return persons[i]; 19 } 20 } 21 public IEnumerator IterMethod2() // [2].函数返回 IEnumerator (推荐) 22 { 23 for (int i = 0; i < persons.Length; ++i) 24 yield return persons[i]; 25 } 26 }
· 主函数测试代码
1 People people = new People(persons); 2 foreach (var person in people) 3 Console.WriteLine(person.ToString());
· · 返回 IEnumerable 类型
返回此类型的迭代器通常用于实现自定义迭代器。
· People 类
1 public class People 2 { 3 private Person[] persons; 4 public People(Person[] _persons){ 5 persons = new Person[_persons.Length]; 6 for (int i = 0; i < _persons.Length; ++i) 7 persons[i] = _persons[i]; 8 } 9 10 public IEnumerator GetEnumerator(){ 11 return IterMethod1.GetEnumerator(); // [1] 12 return IterMethod2().GetEnumerator(); // [2] 13 } 14 public IEnumerable IterMethod1 // [1].自定义迭代器 1 15 { 16 get{ 17 for (int i = 0; i < persons.Length; ++i) 18 yield return persons[i]; 19 } 20 } 21 public IEnumerable IterMethod2() // [2].自定义迭代器 2 (推荐) 22 { 23 for (int i = 0; i < persons.Length; ++i) 24 yield return persons[i]; 25 } 26 }
· 主函数测试代码
1 People people = new People(persons); 2 foreach (var person in people) // 默认 3 Console.WriteLine(person.ToString()); 4 foreach (var person in people.IterMethod1) // [1] 5 Console.WriteLine(person.ToString()); 6 foreach (var person in people.IterMethod2()) // [2] 7 Console.WriteLine(person.ToString());
对于返回泛型IEnumerator<T>、IEnumerable<T>的迭代器,同理。
参考