我们都知道可以用foreach来遍历数组,List或者Dictionary等等。相比for循环来说,foreach写法简单,不用担心越界问题,也不用强制进行类型转换(下文有分析)。
例如将一个double数组的元素当做int类型输出,使用for循环可能需要这样写
for (int i = 0; i < doubleArray.Length; i++)
{
Console.WriteLine((int)doubleArray[i]);
}
但如果使用foreach:
foreach (int i in doubleArray)
{
Console.WriteLine(i);
}
/************************************************/
回到正题,foreach的原理是什么,或者说怎么实现一个可以被foreach遍历的结构呢?
答案主要在于两个C#接口,IEnumerable和IEnumerator。
IEnumerable 接口里有一个方法GetEnumerator。IEnumerable的意思是这个集合是可以遍历的,而GetEnumerator方法返回的IEnumerator的就是一个遍历器,用这个工具来遍历这个类。
IEnumorator接口中定义的内容包括Current,就是返回这个遍历工具所指向的那个容器的当前的元素,MoveNext ()方法就是指向下一个元素,当遍历到最后没有元素时,返回一个false.
上代码:School集合里有很多Student
首先定义一个Student类,这个类里有属性Name和构造器:
class Student
{
public string Name { get; set; };
public Student()
{
}
public Student(string name)
{
Name = name;
}
}
接下来定义School类,其中包含一个Student数组,构造方法。并继承IEnumerable接口并实现IEnumerator IEnumerable.GetEnumerator()方法:
class School : IEnumerable
{
private Student[] students;
public School(Student[] stus)
{
students = (Student[])stus.Clone();
}
IEnumerator IEnumerable.GetEnumerator()
{
return new PeopleEnum(students);
}
}
其中PeopleEnum
是继承了IEnumerator接口的类型,用于foreach遍历。IEnumerator接口中包括了object IEnumerator.Current属性和public bool MoveNext(),public void Reset()方法。
如果使用了泛型T还会有T IEnumerator<T>.Current属性。
class PeopleEnum : IEnumerator
{
private Student[] students;
private int index = -1;
object IEnumerator.Current => Current;
public Student Current
{
get
{
return students[index];
}
}
public PeopleEnum(Student[] stus)
{
students= stus;
}
public bool MoveNext()
{
index++;
return index < students.Length;
}
public void Reset()
{
index = -1;
}
}
我们额外添加了student数组和一个数组下标index。
其中foreach首先调用MoveNext()
方法,使其下标加1,判断是否越界。如果没有越界就通过Current取出目标值。可以看到如果没有使用泛型的话,返回的是object类型的值,这也是为什么foreach不需要我们进行强制类型转换的原因,即foreach会自动进行转换,缺点就是涉及到装箱拆箱,影响效率。但是这一点可以通过使用泛型避免,有点类似于C#中的ArrayList和List。