IEnumerable,IEnumerator vs foreach,何时使用啥[重复]

Posted

技术标签:

【中文标题】IEnumerable,IEnumerator vs foreach,何时使用啥[重复]【英文标题】:IEnumerable , IEnumerator vs foreach, when to use what [duplicate]IEnumerable,IEnumerator vs foreach,何时使用什么[重复] 【发布时间】:2009-07-06 06:13:59 【问题描述】:

我正在通过 IEnumerable 和 IEnumerator ,但无法清楚地得到一点..如果我们有foreach,那么为什么我们需要这两个接口?有没有我们必须使用接口的场景。如果是,那么有人可以举个例子来解释一下。 欢迎任何建议和评论。 谢谢。

【问题讨论】:

【参考方案1】:

foreach使用接口在很多情况下。如果你想实现一个foreach 可以使用的序列,你需要这些接口。 (虽然迭代器块通常使这个实现任务非常简单。)

但是,偶尔直接使用迭代器会很有用。一个很好的例子是尝试“配对”两个不同的序列。例如,假设您收到两个序列 - 一个是姓名,一个是年龄,并且您希望将这两个序列一起打印。你可以写:

static void PrintNamesAndAges(IEnumerable<string> names, IEnumerable<int> ages)

    using (IEnumerator<int> ageIterator = ages.GetEnumerator())
    
        foreach (string name in names)
        
            if (!ageIterator.MoveNext())
            
                throw new ArgumentException("Not enough ages");
            
            Console.WriteLine("0 is 1 years old", name, ageIterator.Current);
        
        if (ageIterator.MoveNext())
        
            throw new ArgumentException("Not enough names");
        

    

同样,如果您想以与其他项目不同的方式处理(例如)第一项,则使用迭代器会很有用:

public T Max<T>(IEnumerable<T> items)

    Comparer<T> comparer = Comparer<T>.Default;

    using (IEnumerator<T> iterator = items.GetEnumerator())
    
        if (!iterator.MoveNext())
        
            throw new InvalidOperationException("No elements");
        
        T currentMax = iterator.Current;

        // Now we've got an initial value, loop over the rest
        while (iterator.MoveNext())
        
            T candidate = iterator.Current;
            if (comparer.Compare(candidate, currentMax) > 0)
            
                currentMax = candidate;
            
        
        return currentMax;
    


现在,如果您对IEnumerator&lt;T&gt;IEnumerable&lt;T&gt; 之间的区别感兴趣,您可能想用数据库术语来考虑它:将IEnumerable&lt;T&gt; 视为一个表,将IEnumerator&lt;T&gt; 视为一个游标。你可以要求一个表给你一个新的游标,你可以在同一个表上同时有多个游标。

真正理解这种差异可能需要一段时间,但只要记住列表(或数组,或其他)没有任何“您在列表中的位置”的概念,而是对该列表/数组的迭代器/whatever does 有这种状态是有帮助的。

【讨论】:

感谢 Jon 的所有帮助。我现在明白了,并且会进一步研究以使其清楚。我还发现以下链接非常有用:***.com/questions/558304/… 这是一个示例,说明您使用 IEnumerator 只是为了保持效率。 ***.com/questions/1018407/… @Jon Skeet 我真的很喜欢你编写 Max 方法的方式。我有一个问题。您使用了使用带有 IEnumberator 的构造。我知道使用处理它里面的东西()。你为什么在 IEnumberator 上使用它? @ImranAmjad: IEnumerator&lt;T&gt; 实现了IDisposable,所以当你完成它时你应该调用Dispose。特别是,如果您使用的序列是从迭代器块生成的,则允许执行 finally 块 - 因此可以从那里释放资源。 哦,我明白了。 IEnumerator 不实现 IDisposible 但 IEnumberator 实现。很高兴知道。此外,如果我们实现 IEnumerator 我们必须实现 Dispose 方法。如您所说,如果生成了序列,则必须将其处理掉。非常感谢!【参考方案2】:

乔恩说了什么。

IEnumerableIEnumerable&lt;T&gt; :通过实现这个,一个对象声明它可以给你一个迭代器,你可以用它来遍历序列/集合/集合 IEnumeratorIEnumerator&lt;T&gt; :如果调用之前接口中定义的 GetEnumerator 方法,则会得到一个迭代器对象作为 IEnumerator 引用。这使您可以调用 MoveNext() 并获取 Current 对象。 foreach :在某种意义上是一个 C# 构造/外观,因为您不需要知道它在幕后是如何工作的。它在内部获取迭代器并调用正确的方法让您专注于您想要对每个项目(foreach 块的内容)执行的操作。大多数情况下,您只需要 foreach ,除非您要实现自己的类型或自定义迭代,在这种情况下,您需要了解前 2 个接口。

【讨论】:

感谢 Gishu 让我更清楚。我也觉得这个链接非常有用:***.com/questions/558304/…【参考方案3】:

请记住,您没有必须实现 IEnumerator 及其变体来使用 foreach - IIRC,它所需要的只是一个 GetEnumerator() 方法,该方法返回一个具有 MoveNext() 的对象方法返回一个布尔值,一个 Current 属性返回一个对象。您不需要使用 IEnumerator 和 IEnumerable,尽管这样做通常是一个非常好的主意。请参阅here 了解更多信息。

【讨论】:

这是一个奇怪的老野兽,那个——你可以实现这两种方法。这样做的最大问题是类型系统不知道这个东西是可枚举的,所以我想你在使用 Linq to Objects 时会遇到麻烦。 是的,这很奇怪。我们支持该功能的原因是您可以在 C# 1.0 中编写整数集合并在不装箱的情况下枚举它们。现在我们有了 IE,就不再需要“模式”方法了。 @Eric Lippert:该功能还有另一个优点:它可以避免结构枚举器的堆分配,尽管我建议 GetEnumerator 方法返回结构的类应该具有 IEnumerable.GetEnumerator 方法返回课程。

以上是关于IEnumerable,IEnumerator vs foreach,何时使用啥[重复]的主要内容,如果未能解决你的问题,请参考以下文章

IEnumerable和IEnumerator 详解

IEnumerator和IEnumerable详解

IEnumerator和IEnumerable详解

IEnumerable和IEnumerator详解

C# IEnumerator IEnumerable接口

IEnumerable和IEnumerator接口