为什么我们需要两个接口来枚举集合?

时间:2017-03-31 08:02:29

标签: c# foreach ienumerable ienumerator

我一直试图理解IEnumerableIEnumerator背后的想法。我阅读了网上可以找到的所有问题和答案,特别是StackOverflow,但我并不满意。我明白了 应该如何使用这些接口,但为什么以这种方式使用它们。

我认为我误解的本质是我们需要两个接口进行一次操作。我意识到如果两者都需要,一个可能还不够。所以我采用了#34;硬编码"相当于foreach(我发现here):

while (enumerator.MoveNext())
{
    object item = enumerator.Current;

    // logic
}

并试图让它与一个界面一起工作,认为会出现问题,这会让我明白为什么需要另一个界面。

所以我创建了一个集合类,并实现了IForeachable

class Collection : IForeachable
{
    private int[] array = { 1, 2, 3, 4, 5 };
    private int index = -1;

    public int Current => array[index];

    public bool MoveNext()
    {
        if (index < array.Length - 1)
        {
            index++;
            return true;
        }

        index = -1;
        return false;
    }
}

并使用foreach等效项来提名集合:

var collection = new Collection();

while (collection.MoveNext())
{
    object item = collection.Current;

    Console.WriteLine(item);
}

它有效!那么这里缺少什么需要另一个接口呢?

感谢。

修改 我的问题不是评论中列出的问题的重复:

  • This问题是为什么首先要进行枚举所需的接口。
  • This问题和this问题是 是什么这些接口以及 应该如何使用。

我的问题是为什么他们按照他们的方式设计,而不是他们是什么 他们的工作方式,以及我们为什么需要他们在第一时间。

2 个答案:

答案 0 :(得分:5)

这两个界面是什么?他们做了什么?

IEnumerable接口放在集合对象上并定义GetEnumerator()方法,它返回一个实现IEnumerator接口的(通常是新的)对象。 C#中的foreach语句和VB.NET中的For Each语句使用IEnumerable来访问枚举器,以便遍历集合中的元素。

IEnumerator接口实际上是放在实际进行迭代的对象上的契约。它存储迭代的状态,并在代码在集合中移动时更新它。

为什么不将集合也作为枚举器呢?为什么有两个独立的接口?

没有什么可以阻止IEnumerator和IEnumerable在同一个类上实现。但是,执行此操作会受到惩罚 - 同一个集合上不可能有两个或更多个循环。如果可以绝对保证不需要同时在集合上循环两次那么那就没问题了。但在大多数情况下是不可能的。

什么时候会有人多次迭代一个集合?

以下是两个例子。

第一个例子是在同一个集合中有两个嵌套在一起的循环。如果集合也是枚举器,那么就不可能在同一个集合上支持嵌套循环,当代码到达内部循环时,它将与外部循环冲突。

第二个示例是当有两个或更多线程访问同一个集合时。同样,如果集合也是枚举器,那么就不可能在同一集合上支持安全的多线程迭代。当第二个线程试图遍历集合中的元素时,两个枚举的状态将发生冲突。

此外,由于.NET中使用的迭代模型不允许在枚举期间更改集合,因此这些操作完全是安全的。

- 这是我多年前写的一篇博文:https://colinmackay.scot/2007/06/24/iteration-in-net-with-ienumerable-and-ienumerator/

答案 1 :(得分:4)

你的IForeachable甚至不能从两个不同的线程迭代(你根本不能有多个活动迭代 - 甚至来自同一个线程),因为当前的枚举状态存储在IForeachable本身。您还必须在每次完成枚举时重置当前位置,如果您忘记这样做 - 那么,下一个调用者会认为您的集合是空的。我只能想象所有可能导致的所有难以追踪的错误。

另一方面,因为IEnumerable为每个调用者返回新的IEnumerator - 您可以同时进行多个枚举,因为每个调用者都有自己的枚举状态。我认为这个原因足以证明两个接口的合理性。枚举本质上是读取操作,如果你不能在多个地方同时读取相同的东西,那将会非常混乱。