创建IEnumerator的原因是什么?

ziz*_*raj 23 c# design-patterns

IEnumerator包含MoveNext(),Reset()Current作为其成员.现在假设我已将这些方法和属性移动到IEnumerable接口并删除了GetEnumerator()方法和IEnumerator接口.

现在,实现的类的对象IEnumerable将能够访问方法和属性,因此可以迭代.

  • 为什么没有遵循上述方法以及如果我遵循它将面临的问题?
  • IEnumerator接口的存在如何解决这些问题?

Jon*_*eet 49

迭代器包含与集合分离的状态:它包含一个光标,表示您集合中的位置.因此,必须有一个独立的对象来表示额外的状态,一种方法来获取该对象和操作上的那个对象-因此IEnumerator(和IEnumerator<T>),GetEnumerator()和迭代器的成员.

想象一下,如果我们没有单独的状态,那么我们写道:

var list = new List<int> { 1, 2, 3 };

foreach (var x in list)
{
    foreach (var y in list)
    {
         Console.WriteLine("{0} {1}", x, y);
    }
}
Run Code Online (Sandbox Code Playgroud)

应该打印"1 1","1 2","1 3","2 1"等...但没有任何额外的状态,它怎么能"知道"两个循环的两个不同位置?


Tho*_*que 25

现在假设我已经将这些方法和属性移动到IEnumerable接口并删除了GetEnumerator()方法和IEnumerator接口.

这样的设计会阻止集合中的并发枚举.如果是跟踪当前位置的集合本身,则不能有多个枚举相同集合的线程,甚至是嵌套的枚举,例如:

foreach (var x in collection)
{
    foreach (var y in collection)
    {
        Console.WriteLine("{0} {1}", x, y);
    }
}
Run Code Online (Sandbox Code Playgroud)

通过将跟踪当前位置的责任委托给不同的对象(枚举器),它使集合的每个枚举独立于其他对象.

  • 你受到了6秒的抨击. (33认同)

fli*_*erg 8

一个很长的答案,前两个答案涵盖了大部分答案,但我发现在C#语言规范中查找foreach时我发现有些方面很有趣.除非您对此感兴趣,否则请停止阅读.

现在转到intering部分,根据C#规范扩展以下声明:

foreach (V v in x) embedded-statement
Run Code Online (Sandbox Code Playgroud)

祝你:

{`
 E e = ((C)(x)).GetEnumerator();
 try {
    while (e.MoveNext()) {
        V v = (V)(T)e.Current;
        embedded-statement
    }
}
 finally {
    … // Dispose e
 }
}
Run Code Online (Sandbox Code Playgroud)

拥有某种身份函数x == ((C)(x)).GetEnumerator()(它是它自己的枚举器)并使用@ JonSkeet的循环产生类似的东西(为了简洁,删除了try/catch):

var list = new List<int> { 1, 2, 3 };

while (list.MoveNext()) {
 int x = list.Current; // x is always 1
 while (list.MoveNext()) {
   int y = list.Current; // y becomes 2, then 3
   Console.WriteLine("{0} {1}", x, y);
 }
}
Run Code Online (Sandbox Code Playgroud)

将打印出以下内容:

1 2
1 3
Run Code Online (Sandbox Code Playgroud)

然后list.MoveNext()false永远回归.如果你看一下,这一点非常重要:

var list = new List<int> { 1, 2, 3 };

// loop
foreach (var x in list) Console.WriteLine(x); // Will print 1,2,3
// loop again
// Will never enter loop, since reset wasn't called and MoveNext() returns false
foreach (var y in list) Console.WriteLine(x); // Print nothing
Run Code Online (Sandbox Code Playgroud)

因此,考虑到上述情况,并注意它完全可行,因为foreach语句GetEnumerator()在检查类型是否实现之前查找-method IEnumerable<T>:

为什么没有遵循上述方法以及如果我遵循它将面临的问题?

您不能嵌套循环,也不能使用该foreach语句多次访问同一个集合而不Reset()在它们之间手动调用.当我们在每次之后处理枚举器时会发生什么foreach

IEnumerator接口的存在如何解决这些问题?

所有迭代都是相互独立的,无论我们是在讨论嵌套,多线程等,枚举都与集合本身是分开的.您可以将其视为有点像关注点或SoC,因为想法是将遍历与实际列表本身分开,并且在任何情况下遍历都不应该改变集合的状态.IE调用MoveNext()您的示例将修改集合.