C# IEnumerable,IEnumerator vs foreach,何时使用什么
我曾经讨论过IEnumerable和IEnumerator,但是没有一点是清楚的。如果我们有foreach,那么为什么我们需要这两个接口呢?是否存在我们必须使用接口的场景。如果是,那么有人可以举例说明。 欢迎提出任何建议和意见。 谢谢。C# IEnumerable,IEnumerator vs foreach,何时使用什么,c#,enumeration,C#,Enumeration,我曾经讨论过IEnumerable和IEnumerator,但是没有一点是清楚的。如果我们有foreach,那么为什么我们需要这两个接口呢?是否存在我们必须使用接口的场景。如果是,那么有人可以举例说明。 欢迎提出任何建议和意见。 谢谢。foreach在许多情况下都使用接口。如果要实现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
:如果调用上一个接口中定义的GetEnumerator方法,则会得到一个迭代器对象作为IEnumerator引用。这使您能够调用MoveNext()并获取当前对象IEnumerator
:在某种意义上,它是一个C#结构/外观,你不需要知道它在引擎盖下是如何工作的。它在内部获取迭代器并调用正确的方法,以便您集中精力处理每个项(foreach块的内容)。大多数时候,您只需要foreach,除非您正在实现自己的类型或自定义迭代,在这种情况下,您需要了解前两个接口foreach
IEnumerator
实现了IDisposable
,所以在使用完它后,您需要调用Dispose
。特别是,如果您使用的序列是从迭代器块生成的,则允许执行finally
块-因此可以从那里释放资源。哦,我看到了区别。IEnumerator不实现IDisposable,但IEnumberator实现。很高兴知道。此外,如果我们实现IEnumerator,我们必须实现Dispose方法。正如您所说的,若生成了序列,那个么必须对其进行处理。谢谢!谢谢吉舒让我更明白。我还发现这个链接非常有用:这是一个奇怪的老怪物,你可以实现这两种方法。这样做的最大问题是类型系统不知道这个东西是可枚举的,所以我想你在使用Linq to对象时会遇到麻烦。是的,这很奇怪。我们支持该特性的原因是,您可以在C#1.0中编写一个整数集合,并在不装箱的情况下枚举它们。现在我们有了IE,就不再需要“模式”方法了。@Eric Lippert:该功能还有另一个优点:它可以避免为结构枚举数分配堆,尽管我建议那些GetEnumerator方法返回结构的类应该有IEnumerable.GetEnumerator方法返回类。