为什么我们需要在c#中使用迭代器?

有人可以提供关于迭代器使用的真实例子。 我试着搜索谷歌,但对答案不满意。

迭代器是一种抽象,它将集合中的位置概念与集合本身分离。 迭代器是一个单独的对象,存储必要的状态以定位集合中的项目并移动到集合中的下一个项目。 我已经看到集合在集合中保持该状态(即当前位置),但通常最好将该状态移动到外部对象。 除此之外,它还允许您使用多个迭代器迭代相同的集合。

您可能听说过数组和容器 – 存储其他对象列表的对象。

但是为了使对象表示列表,它实际上不必“存储”列表。 它所要做的就是为您提供允许您获取列表项的方法或属性。

在.NET框架中,接口IEnumerable是一个对象必须支持在这个意义上被视为“列表”。

为了简化它(省略一些历史包袱):

public interface IEnumerable { IEnumerator GetEnumerator(); } 

所以你可以从中获得一个枚举器。 该界面(再次,略微简化以消除分散注意力的噪音):

 public interface IEnumerator { bool MoveNext(); T Current { get; } } 

因此,要遍历列表,您可以这样做:

 var e = list.GetEnumerator(); while (e.MoveNext()) { var item = e.Current; // blah } 

foreach关键字可以整齐地捕获此模式:

 foreach (var item in list) // blah 

但是创建一种新的列表呢? 是的,我们可以使用List并填充项目。 但是,如果我们想要在需要时“动态”发现这些物品呢? 这有一个优点,即客户端可以在前三项之后放弃迭代,并且它们不必“支付生成整个列表的成本”。

手动实现这种懒惰列表会很麻烦。 我们必须编写两个类,一个用于通过实现IEnumerable来表示列表,另一个用于通过实现IEnumerable来表示活动的枚举操作。

迭代器方法为我们做了所有艰苦的工作。 我们只写:

 IEnumerable GetNumbers(int stop) { for (int n = 0; n < stop; n++) yield return n; } 

编译器将此转换为两个类。 调用该方法等同于构造表示该列表的类的对象。

简单示例:生成整数序列的函数:

 static IEnumerable GetSequence(int fromValue, int toValue) { if (toValue >= fromValue) { for (int i = fromValue; i <= toValue; i++) { yield return i; } } else { for (int i = fromValue; i >= toValue; i--) { yield return i; } } } 

要在没有迭代器的情况下执行此操作,您需要创建一个数组然后枚举它…

通过课堂上的学生进行迭代

Iterator设计模式为我们提供了枚举项目或数组列表的常用方法,同时隐藏了列表实现的细节。 这样可以更清晰地使用数组对象,并从客户端隐藏不必要的信息,最终实现更好的代码重用,增强的可维护性和更少的错误。 迭代器模式可以枚举项目列表,而不管它们的实际存储类型如何。

迭代一系列作业问题。

但严重的是,迭代器可以提供统一的方式来遍历集合中的项目,而不管底层数据结构如何。

请阅读前两段以获取更多信息。

他们非常适合的一些事情:
a)在保持代码整洁的同时“感知性能” – 与其他处理逻辑分离的东西的迭代。
b)当您要迭代的项目数量未知时。

虽然两者都可以通过其他方式完成,但是使用迭代器可以使代码变得更好更整洁,因为调用迭代器的人不需要担心它如何找到迭代的东西……

现实生活中的例子:枚举目录和文件,找到满足某些条件的第一个[n],例如包含某个字符串或序列等的文件……

除了其他一切,迭代懒惰型序列 – IEnumerators。 可以在迭代步骤中评估/初始化这种序列的每个下一个元素,这使得可以使用有限量的资源迭代无限序列……

规范和最简单的例子是它使无限序列成为可能,而没有必须自己编写类来完成这一过程的复杂性:

 // generate every prime number public IEnumerator GetPrimeEnumerator() { yield return 2; var primes = new List(); primesSoFar.Add(2); Func IsPrime = n => primes.TakeWhile( p => p <= (int)Math.Sqrt(n)).FirstOrDefault(p => n % p == 0) == 0; for (int i = 3; true; i += 2) { if (IsPrime(i)) { yield return i; primes.Add(i); } } } 

显然这不会是真正的无限,除非你使用BigInt而不是int但它给你的想法。 为每个生成的序列编写此代码(或类似代码)将是乏味且容易出错的。 迭代器为你做这件事。 如果上面的例子看起来太复杂,你可以考虑:

 // generate every power of a number from start^0 to start^n public IEnumerator GetPowersEnumerator(int start) { yield return 1; // anything ^0 is 1 var x = start; while(true) { yield return x; x *= start; } } 

他们付出了代价。 它们的惰性行为意味着您无法发现常见错误(空参数等),直到首次使用生成器而不是创建而不先写入包装函数进行检查。 如果递归使用,当前的实现也非常糟糕(1)。

由于状态维护主要是为您完成的,所以在树和对象图之类的复杂结构上更容易编写枚举更容易编写,您必须简单地编写代码来访问每个项目而不必担心回到它。


  1. 我不轻易使用这个词 – O(n)迭代可以变成O(N ^ 2)

迭代器是实现IEnumerator接口的简单方法。 您只需创建一个逐个返回值的方法,然后编译器创建一个具有实现接口所需的方法和属性的类,而不是创建具有接口所需的方法和属性的类。

例如,如果您有一个很大的数字列表,并且想要返回一个集合,其中每个数字乘以2,您可以创建一个返回数字的迭代器,而不是在内存中创建列表的副本:

 public IEnumerable GetDouble() { foreach (int n in originalList) yield return n * 2; } 

在C#3中,您可以使用扩展方法和lambda表达式执行类似的操作:

 originalList.Select(n => n * 2) 

或者使用LINQ:

 from n in originalList select n * 2 
 IEnumerator myIterator = listOfStackOverFlowQuestions.GetEnumerator(); while (myIterator.MoveNext()) { Question q; q = myIterator.Current; if (q.Pertinent == true) PublishQuestion(q); else SendMessage(q.Author.EmailAddress, "Your question has been rejected"); } foreach (Question q in listOfStackOverFlowQuestions) { if (q.Pertinent == true) PublishQuestion(q); else SendMessage(q.Author.EmailAddress, "Your question has been rejected"); }