一.迭代器
学过C++的都知道,迭代器是STL中的一个类,用来遍历容器。
vector<int>::iterator iter;
for (iter = v1.begin(); iter != v1.end(); iter++)
C#中迭代器也是用来遍历集合,本质上是一个类/接口(IEnumerator),可以解决在不知道集合以何种key存储的情况下,对集合进行遍历。
int[] nums = {1, 2, 3, 4, 5};
foreach(int i in nums)
{
Console.WriteLine(i);
}
可以看到C#的迭代器i,没有初始值,结束条件,变化的情况;这是因为C#中的foreach隐藏了迭代器的复杂性,foreach是一种语法糖。可以用foreach遍历的集合必须实现接口IEnumerable,IEnumerator.
二.IEnumerator接口
public interface IEnumerator
{
bool MoveNext();
object Current { get; }
void Reset();
}
MoveNext
将当前元素向前移动到下一个位置,如果集合没有更多元素,那么它会返回false。Current
返回当前位置元素。在取得第一个元素之前 必须先调用MoveNext
,即使空集合也支持该操作。Reset
作用就是当前位置移回起点,并允许再一次枚举集合。此方法一般并不建议使用,因为完全可以重新实例化一个枚举器。
三.IEnumerable接口
public interface IEnumerable:
{
IEnumerator GetEnumerator();
}
通过GetEnumerator
返回枚举器,IEnumerable
可以看作IEnumerator
的提供者。
下面例子演示foreach是一种语法糖,本质是调用接口IEnumerator的MoveNext方法和Current属性
class MainClass
{
static void Main()
{
string s = "Hello";
IEnumerator<char> iter = s.GetEnumerator(); //通过GetEnumerator方法获取迭代器
while (iter.MoveNext()) //通过MoveNext方法判断是否还有下一个元素,并向后移动
{
char c = (char)iter.Current; //通过Current属性获取元素
Console.Write(c + "_");
}
Console.WriteLine();
foreach(char c in s)
{
Console.Write(c + ".");
}
Console.WriteLine();
}
}
/* Output:
H_e_l_l_o
H.e.l.l.o
*/
四.遍历自定义集合的做法
下面的代码示例演示自定义集合的IEnumerable和IEnumerator接口的实现。 在此示例中,不会显式调用这些接口的成员,它们实现为支持使用foreach循环访问集合。
using System;
using System.Collections;
// Simple business object.
public class Person
{
public Person(string fName, string lName)
{
this.firstName = fName;
this.lastName = lName;
}
public string firstName;
public string lastName;
}
// Collection of Person objects. This class
// implements IEnumerable so that it can be used
// with ForEach syntax.
public class People : IEnumerable
{
private Person[] _people;
public People(Person[] pArray)
{
_people = new Person[pArray.Length];
for (int i = 0; i < pArray.Length; i++)
{
_people[i] = pArray[i];
}
}
// Implementation for the GetEnumerator method.
IEnumerator IEnumerable.GetEnumerator()
{
return (IEnumerator) GetEnumerator();
}
public PeopleEnum GetEnumerator()
{
return new PeopleEnum(_people);
}
}
// When you implement IEnumerable, you must also implement IEnumerator.
public class PeopleEnum : IEnumerator
{
public Person[] _people;
// Enumerators are positioned before the first element
// until the first MoveNext() call.
int position = -1;
public PeopleEnum(Person[] list)
{
_people = list;
}
public bool MoveNext()
{
position++;
return (position < _people.Length);
}
public void Reset()
{
position = -1;
}
object IEnumerator.Current
{
get
{
return Current;
}
}
public Person Current
{
get
{
try
{
return _people[position];
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException();
}
}
}
}
class App
{
static void Main()
{
Person[] peopleArray = new Person[3]
{
new Person("John", "Smith"),
new Person("Jim", "Johnson"),
new Person("Sue", "Rabon"),
};
People peopleList = new People(peopleArray);
foreach (Person p in peopleList)
Console.WriteLine(p.firstName + " " + p.lastName);
}
}
/* This code produces output similar to the following:
*
* John Smith
* Jim Johnson
* Sue Rabon
*
*/
五.IEnumerable返回IEnumerator的作用
先说结论,如果集合实现IEnumerator的话,存在问题。
当出现循环嵌套时,内循环跳出时会把index制为-1,外循环永远无法结束,造成死循环。
为了解决这一问题,IEnumerable的GetEnumerator方法会返回一个new出来的IEnumerator对象