C# IEnumerable,IEnumerator vs foreach,何时使用什么

C# IEnumerable,IEnumerator vs foreach,何时使用什么,c#,enumeration,C#,Enumeration,我曾经讨论过IEnumerable和IEnumerator,但是没有一点是清楚的。如果我们有foreach,那么为什么我们需要这两个接口呢?是否存在我们必须使用接口的场景。如果是,那么有人可以举例说明。 欢迎提出任何建议和意见。 谢谢。foreach在许多情况下都使用接口。如果要实现foreach可以使用的序列,则需要这些接口。(但迭代器块通常使实现任务非常简单。) 但是,偶尔直接使用迭代器也会很有用。一个很好的例子是尝试“配对”两个不同的序列。例如,假设您收到两个序列—一个是姓名序列,一个是年

我曾经讨论过IEnumerable和IEnumerator,但是没有一点是清楚的。如果我们有foreach,那么为什么我们需要这两个接口呢?是否存在我们必须使用接口的场景。如果是,那么有人可以举例说明。 欢迎提出任何建议和意见。 谢谢。

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");
        }

    }
}
static void PrintNamesAndAges(IEnumerable名称、IEnumerable年龄)
{
使用(IEnumerator ageIterator=ages.GetEnumerator())
{
foreach(名称中的字符串名称)
{
如果(!ageIterator.MoveNext())
{
抛出新的ArgumentException(“年龄不够”);
}
WriteLine(“{0}已经{1}年了”,名称,ageIterator.Current);
}
if(ageIterator.MoveNext())
{
抛出新ArgumentException(“名称不足”);
}
}
}
同样,如果您希望以不同的方式对待(比如)第一项和其他项,那么使用迭代器也很有用:

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;
    }
}
public T Max(IEnumerable items)
{
Comparer=Comparer.Default;
使用(IEnumerator迭代器=items.GetEnumerator())
{
如果(!iterator.MoveNext())
{
抛出新的InvalidOperationException(“无元素”);
}
T currentMax=迭代器.Current;
//现在我们有了一个初始值,循环其余部分
while(iterator.MoveNext())
{
T候选者=迭代器.Current;
如果(比较器比较(候选,当前最大)>0)
{
currentMax=候选者;
}
}
返回电流最大值;
}
}

现在,如果您对
IEnumeral
IEnumerable
之间的区别感兴趣,您可能希望从数据库的角度来考虑它:将
IEnumerable
视为一个表,将
IEnumerable
视为一个光标。您可以要求一个表给您一个新光标,并且可以在同一个表上同时有多个光标

可能需要一段时间才能真正体会到这种差异,但只要记住列表(或数组,或其他任何东西)没有任何“你在列表中的位置”的概念,但是对列表/数组/任何有这种状态的东西的迭代器是有帮助的。

Jon说的话

  • IEnumerable
    IEnumerable
    :通过实现这一点,对象声明它可以为您提供一个迭代器,您可以使用它遍历序列/集合/集合
  • IEnumerator
    IEnumerator
    :如果调用上一个接口中定义的GetEnumerator方法,则会得到一个迭代器对象作为IEnumerator引用。这使您能够调用MoveNext()并获取当前对象
  • foreach
    :在某种意义上,它是一个C#结构/外观,你不需要知道它在引擎盖下是如何工作的。它在内部获取迭代器并调用正确的方法,以便您集中精力处理每个项(foreach块的内容)。大多数时候,您只需要foreach,除非您正在实现自己的类型或自定义迭代,在这种情况下,您需要了解前两个接口

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

感谢Jon为您提供的帮助。我现在就知道了,并将进一步研究以弄清楚。此外,我发现以下链接非常有用:这是一个示例,说明您在使用IEnumerator时为了保持效率而遇到的困难@Jon Skeet我真的很喜欢你编写Max方法的方式。我有一个问题。您使用了带有IEnumberator的构造函数。我知道使用会处理其中的内容()。为什么要在IEnumberator上使用它?@ImranAmjad:
IEnumerator
实现了
IDisposable
,所以在使用完它后,您需要调用
Dispose
。特别是,如果您使用的序列是从迭代器块生成的,则允许执行
finally
块-因此可以从那里释放资源。哦,我看到了区别。IEnumerator不实现IDisposable,但IEnumberator实现。很高兴知道。此外,如果我们实现IEnumerator,我们必须实现Dispose方法。正如您所说的,若生成了序列,那个么必须对其进行处理。谢谢!谢谢吉舒让我更明白。我还发现这个链接非常有用:这是一个奇怪的老怪物,你可以实现这两种方法。这样做的最大问题是类型系统不知道这个东西是可枚举的,所以我想你在使用Linq to对象时会遇到麻烦。是的,这很奇怪。我们支持该特性的原因是,您可以在C#1.0中编写一个整数集合,并在不装箱的情况下枚举它们。现在我们有了IE,就不再需要“模式”方法了。@Eric Lippert:该功能还有另一个优点:它可以避免为结构枚举数分配堆,尽管我建议那些GetEnumerator方法返回结构的类应该有IEnumerable.GetEnumerator方法返回类。