一、枚举
1.1 IEnumerator 接口
IEnumerator 接口是用于支持集合类的迭代的接口。它定义了用于访问集合中元素的成员,并提供了一种统一的方式来遍历集合中的元素。
IEnumerator 接口定义了以下成员:
- Current 属性:获取集合中当前位置的元素。
- MoveNext() 方法:将迭代器推进到集合中的下一个元素。
- Reset() 方法:将迭代器重置到集合的开头。
使用 IEnumerator 接口遍历集合:
- 要使用 IEnumerator 接口遍历集合,首先需要获取集合的 IEnumerator 对象,通常使用集合的 GetEnumerator() 方法来实现。
- 调用 MoveNext() 方法将迭代器推进到集合中的下一个元素,并返回一个布尔值指示是否还有更多的元素可供遍历。
- 使用 Current 属性获取当前位置的元素值。
- 如果需要重新开始遍历,可以调用 Reset() 方法将迭代器重置到集合的开头。
以下是一个示例代码,演示了如何使用 IEnumerator 接口遍历集合:
ArrayList list = new ArrayList();
list.Add("Apple");
list.Add("Banana");
list.Add("Orange");
IEnumerator enumerator = list.GetEnumerator();
while (enumerator.MoveNext())
{
string fruit = (string)enumerator.Current;
Console.WriteLine(fruit);
}
enumerator.Reset();
while (enumerator.MoveNext())
{
string fruit = (string)enumerator.Current;
Console.WriteLine(fruit);
}
在上述示例中,我们创建了一个 ArrayList 集合,并向其中添加了几个元素。然后,我们使用集合的 GetEnumerator() 方法获取一个 IEnumerator 对象,用于遍历集合。
通过调用 MoveNext() 方法,我们将迭代器推进到集合中的下一个元素。在每次循环中,我们使用 Current 属性获取当前位置的元素值,并将其转换为 string 类型。
在第一个循环结束后,我们调用 Reset() 方法将迭代器重置到集合的开头。然后,再次使用 MoveNext() 和 Current 遍历集合,输出相同的元素。
需要注意的是,迭代器模式在遍历过程中通常会检测集合的结构是否发生了变化,以确保遍历的安全性。如果在遍历过程中修改了集合的结构(如添加、删除元素),可能会引发 InvalidOperationException 异常。因此,在使用迭代器遍历集合时,应注意避免修改集合的结构。
1.2 foreach 语句
foreach 语句是用于遍历集合或数组的循环结构。它提供了一种简洁的方式来逐个访问集合中的元素,而无需使用索引或迭代器。
语法:
foreach (var item in collection)
{
// 执行操作
}
item是一个变量,用于存储集合中的当前元素值。
- collection 是一个实现了 IEnumerable 或 IEnumerable 接口的集合或数组。
- 在循环体内,可以对 item 进行操作,访问集合中的元素。
foreach 语句的特点:
- foreach 语句提供了一种简洁的方式来遍历集合或数组中的元素,无需使用索引或迭代器。
- foreach 语句会自动处理集合的迭代过程,使得代码更加清晰和易读。
- foreach 语句在循环开始时会自动获取集合的迭代器,并在每次迭代时更新当前元素。
foreach 语句的底层原理:
- foreach 语句的底层实现依赖于 IEnumerable 接口和迭代器模式。
- 集合类或数组类实现了 IEnumerable 或 IEnumerable 接口,该接口定义了 GetEnumerator() 方法,用于返回一个实现了 IEnumerator 或 IEnumerator 接口的迭代器对象。
- foreach 语句在编译时会被转换为使用迭代器的代码,以实现对集合的遍历。
- 在循环开始时,foreach 语句会调用集合的 GetEnumerator() 方法获取一个迭代器对象。
- 然后,使用迭代器的 MoveNext() 方法将迭代器推进到集合中的下一个元素,并使用 Current 属性获取当前元素的值。
- 在每次循环迭代中,循环体内的代码会对当前元素进行操作。
- 当迭代结束时,或者遇到 break 语句时,循环退出。
需要注意的是,使用 foreach 语句遍历集合时,不应在循环过程中修改集合的结构(如添加、删除元素),否则可能会引发异常。foreach 语句在循环开始时获取了集合的迭代器,如果在迭代过程中修改了集合的结构,会导致迭代器失效,从而引发 InvalidOperationException 异常。
1.3 yield 语句
yield 语句用于创建迭代器(iterator),它提供了一种简洁的方式来实现可枚举类型(enumerable types)的迭代。通过使用 yield 语句,可以在迭代过程中逐个返回元素,而无需显式地实现迭代器的接口和方法。
语法:
- yield 语句通常与一个迭代器方法(iterator method)一起使用。迭代器方法是一种特殊的方法,它使用 yield 语句来指示迭代过程的逻辑。
- IEnumerable<T>:迭代器方法返回类型应为 IEnumerable<T>,表示它可以产生一个序列的元素。
- yield return:使用 yield return 语句返回序列中的元素。可以在迭代过程中多次使用 yield return 语句返回多个元素。
- yield break:使用 yield break 语句提前终止迭代,返回到调用代码。
迭代器方法的基本语法如下:
public IEnumerable<T> MyIterator()
{
// 迭代逻辑
yield return element;
}
yield 语句的特点:
- 使用 yield 语句可以使迭代过程更加简洁和易读,避免了手动实现迭代器的复杂性。
- yield 语句将迭代逻辑与产生序列的元素分离,使代码更加模块化和可维护。
yield 语句的底层原理:
- yield 语句的底层原理基于状态机(state machine)的概念。
- 当编译器遇到包含 yield 语句的迭代器方法时,它将生成一个状态机类(state machine class)来处理迭代逻辑。
- 状态机类实现了 IEnumerator<T> 接口,并使用状态来追踪迭代过程。
- 每次调用迭代器方法时,会创建一个状态机对象的实例,并返回该实例的迭代器。
- 在每次调用迭代器的 MoveNext() 方法时,状态机根据当前状态执行相应的逻辑,并返回下一个元素的值。
- 使用 yield return 语句返回元素时,状态机会将当前状态保存下来,并在下一次调用 MoveNext() 时继续执行。
- 当遇到 yield break 语句或迭代结束时,状态机停止迭代,并将迭代器标记为结束。
下面是一个使用 yield 语句的简单示例,演示了如何使用 yield 语句来创建一个简单的迭代器方法:
static void Main(string[] args)
{
foreach (var number in GetNumbers())
{
Console.WriteLine(number);
}
}
public static IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
}
在上述示例中,GetNumbers() 方法是一个迭代器方法,返回类型为 IEnumerable<int>。通过使用 yield return 语句,每次迭代时返回一个整数元素。在 Main() 方法中,使用 foreach 循环来遍历迭代器方法返回的序列,并输出每个元素的值。
需要注意的是,使用 yield 语句创建的迭代器是惰性求值的。即在迭代过程中,元素是按需生成的,只有在调用者请求时才会计算和返回元素值。这种惰性求值的特性可以提高性能和节省内存,尤其在处理大型数据集时。