【问题标题】:Why do we need two interfaces to enumerate a collection?为什么我们需要两个接口来枚举一个集合?
【发布时间】:2017-08-25 10:09:03
【问题描述】:

很长一段时间以来,我一直试图理解IEnumerableIEnumerator 背后的想法。我阅读了所有可以在网上找到的问题和答案,尤其是在 StackOverflow 上,但我并不满意。我明白了应该如何使用这些接口,但不知道为什么要这样使用它们。

我认为我误解的本质是一个操作需要两个接口。我意识到,如果两者都需要,一个可能还不够。所以我采用了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);
}

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

谢谢。


编辑: 我的问题与 cmets 中列出的问题不重复:

  • This 问题是为什么首先需要接口来进行枚举。
  • This question 和 this question 是关于什么这些接口以及应该如何使用它们

我的问题是为什么它们的设计方式是这样的,而不是什么它们是什么,它们是如何工作的,以及我们为什么需要首先是他们。

【问题讨论】:

  • 那么你怎么能同时从两个不同的线程遍历你的集合呢?
  • 想象一下,你想迭代一个文件;您必须打开迭代关闭它;将这些操作隐藏在IEnumerator&lt;T&gt; 中似乎是合理的
  • while 中插入一些条件,然后再次执行另一个循环,你会得到启发。

标签: c# foreach ienumerable ienumerator


【解决方案1】:

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

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

【讨论】:

  • 我认为这里的线程问题是一个红鲱鱼。重要的问题是您将无法同时进行两次活动迭代。他们很可能来自同一个线程。 +1
  • @InBetween 我同意这一点(更新的答案),但我认为线程问题最容易理解。
  • 嘿!感谢您的回答。我对线程一无所知,所以我选择了@ColinMackay 答案,因为它的详细程度和嵌套循环的示例。再次感谢!
【解决方案2】:

这两个接口是什么,它们的作用是什么?

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/

【讨论】:

  • 多个 IEnumerator 的另一点是它们可以实现不同的行为,例如从最后一项开始并向后移动的反向枚举器。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-12
  • 1970-01-01
  • 1970-01-01
  • 2012-04-02
  • 2019-06-27
  • 1970-01-01
相关资源
最近更新 更多