C#迭代器设计的基本原理(与C++相比)

gre*_*man 7 c# iterator

我发现了类似的主题:
C++(stl)与Java中的迭代器,是否存在概念上的差异?

这基本上处理Java迭代器(类似于C#)无法后退.

所以在这里我想关注限制 - 在C++迭代器中不知道它的限制,你自己将给定的迭代器与限制进行比较.在C#中,迭代器知道更多 - 如果迭代器有效,你可以在不与任何外部引用比较的情况下判断.

我更喜欢C++方式,因为一旦有了迭代器,你可以将任何迭代器设置为限制.换句话说,如果您只想获得少量元素而不是整个集合,则不必更改迭代器(在C++中).对我来说它更"纯粹"(清晰).

但是当然MS在设计C#时知道这个和C++.那么C#方式的优点是什么?哪种方法更强大(这导致基于迭代器的更优雅的功能).我错过了什么?

如果您对C#与C++迭代器设计的想法不同于它们的限制(边界),请同时回答.

注意 :(以防万一)请保持严格的技术讨论.没有C++/C#flamewar.

EDITS

正如Tzaman所说:"限制单独表达没有任何好处,因为除了一次走一个元素之外没有办法到达那里." 但是,构建一个一次执行几个步骤的C#迭代器并不困难,因此问题是 - 是否有一个显式限制迭代器的好处(如在C++中)?如果是 - 什么?

@乔恩,

Edit1:假设你有一个函数foo,它在迭代器上做了一些事情(这个例子很天真!)

void foo(iter_from,iter_end) // C++ style
void foo(iter) // C# style 
Run Code Online (Sandbox Code Playgroud)

现在你想在除了最后10个之外的所有元素上调用功能栏.

bar(iter_from,iter_end-10); // C++ style
Run Code Online (Sandbox Code Playgroud)

在C#中(如果我没有记错的话)你必须为这个迭代器提供额外的方法来改变它的限制,如下所示:

bar(iter.ChangeTheLimit(-10));
Run Code Online (Sandbox Code Playgroud)

编辑2:重读你的帖子后,我感觉到了至关重要的区别.在C++中,您处理集合的迭代器,在C#中您处理集合(迭代器在内部使用).如果是的话,我仍然对C#感到有点不舒服 - 你迭代了这个集合,当你找到有趣的元素时,你想要将所有元素从"here"传递到结尾.在C++中,它非常简单,没有开销.在C#中,您要么传递一个迭代器,要么传递一个集合(如果后者会有额外的计算).我等你的评论:-)

@Hans,

我不是在比较苹果和橘子.比较.理论在这里是共同点,所以你有排序算法,分区等.你有收集的概念(或序列,如乔恩喜欢).现在 - 问题是你如何设计对元素的访问,以便用C#或C++(或任何其他语言)编写优雅的算法.我想理解"我们这样做的原因,因为......".

我知道.NET迭代器和集合是单独的类.我知道访问元素和整个集合之间的区别.但是在C++中,处理集合的最常用方法是使用迭代器 - 这样您就可以使用列表和向量,尽管这些集合完全不同.另一方面,在C#中你更愿意写

sort(IEnumerable<T> coll) 
Run Code Online (Sandbox Code Playgroud)

功能而不是

sort(IEnumerator<T> iter)
Run Code Online (Sandbox Code Playgroud)

正确?所以在这个意义上我猜你不能把C#迭代器作为C++迭代器,因为C#不像C++那样表达相同的算法.或者正如Jon指出的那样 - 在C#中你更倾向于转换集合(Skip,Take)而不是改变迭代器.

Jon*_*eet 6

我觉得C#设计更加封装:一个序列耗尽或完成,独立于其他任何东西.它在哪里做的意义,以一个限制与另一个比较?

如果您只想采用一些元素,那么LINQ提供了许多方法来构建另一个序列,例如

foo.Take(10)
foo.Skip(10)
foo.Where(x => x.StartsWith("y"))
Run Code Online (Sandbox Code Playgroud)

等等

我认为将一个序列转换为另一个序列比使用限制指定它更清晰 - 更可组合.如果要将迭代器传递给另一个函数,但是要将其限制为前几个元素,为什么还要传递限制?为什么不通过自我限制的转化序列?

编辑:要解决您的问题编辑:在C#中(至少使用LINQ)您不会修改现有的集合.您将从旧序列创建一个新序列.这是懒洋洋地完成的; 它不会创建新副本或类似的东西.对于LINQ to Objects,这是使用扩展方法执行的IEnumerable<T>,因此任何序列都可以获得相同的功能.

请注意,这不仅限于传统集合 - 它可以是从日志文件中读取的一系列行(再次,懒惰).你不需要任何关于集合本身的知识,只是它是一个可以绘制项目的序列.a IEnumerable<T>和a 之间也存在差异IEnumerator<T>,第一个表示序列,第二个表示序列上的迭代器; IEnumerator<T>很少在C#中明确使用,或传递.

现在,你的"除了最后10个之外的所有元素"的例子是一个棘手的例子,因为对于一般序列,你不能告诉你从结尾开始直到10个元素.LINQ to Objects中没有任何内容可以明确地执行此操作.任何实施ICollectionICollection<T>你可以使用的东西

Bar(list.Take(list.Count - 10))
Run Code Online (Sandbox Code Playgroud)

但那不是很一般.更通用的解决方案是需要维持10个元素的循环缓冲区,有效地在它产生的位置之前读取10个元素.说实话,我很少发现这是一个要求.


tza*_*man 6

我想只要你只谈论前向迭代器,我就喜欢C#方式 - 序列知道它自己的限制,你可以通过LINQ轻松转换它,依此类推.限制单独表达没有任何好处,因为除了一次走一个元素之外没有办法到达那里.

但是,C++迭代器功能更强大 - 您可以使用双向迭代器或随机访问迭代器,它们可以让您执行单向顺序迭代器无法实现的功能.顺序迭代器在某种意义上是链表的泛化,而随机访问迭代器是数组的泛化.这就是为什么你可以在C++迭代器方面有效地表达像quicksort这样的算法,而这是不可能的IEnumerable.


Han*_*ant 2

您正在比较苹果和橙子。STL 集合类具有完全不同的设计目标。它们是一种一刀切的集合设计,您只能获得所提供的类。无法自定义集合,STL 类不是设计来继承的。

与 .NET 非常不同,它们的核心是 ICollection、IDictionary 和 IList 接口。.NET 框架包含数百个集合类,这些集合类经过定制以完成特定的工作。

这也影响了他们迭代器的设计。STL 迭代器必须提供很大的灵活性,因为它们迭代的集合无法自定义。与此相关的是固有成本。有 5 种不同的 STL 迭代器类型,并非所有集合类都支持所有 5 种不同类型。在库设计中失去通用性从来都不是一件好事。

虽然有人认为 C++ 迭代器更灵活,但我会提出相反的主张。IEnumerator 的简单性是非常有价值的 .NET 功能的推动者。例如,C#的yield关键字将集合与迭代器的行为完全分离。如果不是简单的迭代器类型,Linq 就不会出现。协方差支持是不可能的。

关于您的Edit2,不,.NET 迭代器是单独的类,就像 C++ 的迭代器一样。确保理解 IEnumerable(由集合实现)和 IEnumerator(由迭代器实现)之间的区别。