到目前已知的:

  1. foreach是通过枚举器实现遍历的语法糖实现
  2. foreach遍历不能修改元素或者增删元素

无法修改元素

表面上来说是因为current设置为只读

1
2
3
4
5
6
7
8
9
10
11
public interface IEnumerator<out T> : IDisposable, IEnumerator
{
// Returns the current element of the enumeration. The returned value is
// undefined before the first call to MoveNext and following a
// call to MoveNext that returned false. Multiple calls to
// GetCurrent with no intervening calls to MoveNext
// will return the same object.
new T Current
{
get;
}

但就算设为set,也没用。

对于引用类型,current是当前遍历元素的深拷贝复制品,

对于值类型,current只是栈上另起的一块位置,有着相同值罢了

如Dictionary内的引用类型current返回

1
2
3
4
5
6
7
8
9
10
 while ((uint)_index < (uint)_dictionary._count)
{
ref Entry entry = ref _dictionary._entries![_index++];

if (entry.next >= -1)
{
_current = new KeyValuePair<TKey, TValue>(entry.key, entry.value);
return true;
}
}

无法增删元素

  • 代码上来说

    在容器类中都有一个字段,名为_version,其作用是:维持枚举器的一致性

    在容器进行增删改的时候,它都会自增一,在迭代器结构中保存了调用时的version,每次MoveNext时都会判断是否相同则报错

    ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion()

  • 从数据结构来说

    如果增删元素,容器结构就会被改变,就可能对遍历结果造成或多或少的影响,这个要根据情况具体分析。

    例如链表linkedlist 在当前遍历节点之前增删就有影响,在其之后就无影响。

总结:要明确一点,迭代器遍历的目标是实现对各类数据容器的遍历,所做一切无不是为了保证这一功能正常执行,要尽量避免影响该功能的任何其他情况。