订单相关性是否隐含在IEnumerable<;T>;或者应该是明确的
本文关键字:或者 gt lt 是否 相关性 IEnumerable | 更新日期: 2023-09-27 18:26:09
对我来说,C#(好吧,.NET)中的IEnumerable<T>
表示可以迭代的任意数据集。它可以由任何东西支持,比如SELECT
查询的结果、数组的内容、用户在控制台中键入的字符或Pi的数字。数据不能被索引引用,它不一定是有限的或无限的,也不能被修改。当以后调用相同时,它甚至可能是一组不同的数据,比如随机数的IEnumerable<double>
。它只是像foreach
循环一样向消费者提供的一些数据。
现在考虑一下其他处理数据集的概念:SQL。在SQL中,除非明确指定,否则不能保证行的顺序,也不相关。例如,如果您执行SELECT * FROM stack_overflow_posts LIMIT 1
,则数据库不会暗示您返回的行实际上是插入的第一行,也不是最旧的行。例如,您需要使用ORDER BY posted_date_time
对结果进行显式排序。
同样的概念适用于.NET中带有IEnumerable<T>
的枚举吗使用IEnumerable<T>
是否意味着结果总是按特定顺序产生在我前面给出的例子中,我会说是的,顺序是隐含的,因为如果按不同的顺序枚举,结果将毫无意义;如果用户在控制台中键入的字符与实际的击键顺序不同,那么读取它们有什么意义?很明显,LINQ有OrderBy()
可以根据您的需要对结果进行排序,但这是显式排序,而不是隐式排序。
实现接口的类有效地保证遵循特定的模式,而不仅仅是实现接口定义的方法。IEnumerable<T>
是否意味着其数据将按相关顺序生成,或者如果枚举的消费者希望这样做,则由他们明确地进行排序?如果我有一个方法以未定义的顺序生成项目——或者更确切地说,是一个与消费者无关且随时可能更改的订单——我应该使用IEnumerable<T>
以外的东西吗?
IEnumerable<T>
的使用是否意味着结果总是以特定的顺序产生?
没有。IEnumerable
只是保证对象可以被迭代。例如,List<T>
总是根据索引按升序输出项目,这是List<T>
的具体实现细节。
实现接口的类有效地保证遵循特定的模式,而不仅仅是实现接口定义的方法。
IEnumerable<T>
是否意味着其数据将按相关顺序生成,或者如果枚举的消费者希望这样做,则由他们明确排序?
不,实现IEnumerable
并不意味着任何顺序。当使用IEnumerable
的对象时,如果要保证数据每次都以相同的顺序出现,则必须显式提供顺序。
如果您想到实现IEnumerable
的CLR集合类型,这很简单。假设您创建了一个返回IEnumerable<string>
的方法。该方法可以返回一个List<string>
,其IEnumerable
的实现确实有特定的顺序,但它也可以很容易地返回HashSet<string>
,对于它来说,顺序是没有意义的。
如果我有一个方法以未定义的顺序生成项目——或者更确切地说,是一个与消费者无关且随时可能更改的订单——我应该使用
IEnumerable<T>
以外的东西吗?
我认为IEnumerable<T>
非常适合您的需求。为了更加清楚,您可以记录您的方法,并声明结果中项目的顺序是未定义的,并且可以在不同的调用之间更改。
IEnumerable<T>
不保证订单,但实现者,或者返回IEnumerable<T>
的方法可以保证订单。例如,File.ReadLines
保证按顺序提供文件行。Enumerable.Where
保证保持顺序。然而,正如您所提到的,您可以很容易地编写一个方法,每次都会产生一个新的GUID,而且它没有顺序。
IEnumerable<T>
的一个不幸限制是,尽管大多数实现都具有许多特性,而且一些消费者也依赖这些特性,但对象无法指示它们是否具有这些特性。
在这些特征中,如果接收IEnumerable<T>
的方法调用GetEnumerator
并遍历集合的内容,则它将在有限数量的MoveNext
调用后到达末尾;如果它再次调用GetEnumerator
而没有对传入的IEnumerable<T>
执行任何其他操作,那么它应该以相同的顺序接收相同的项目序列并在相同的点结束。
请注意,上述内容实际上包含了许多离散的标准;有许多实现可以满足所有要求,但实现几乎可以满足任何组合。在上述声明的不同标准中:
-
枚举将产生有限数量的项。
-
多个枚举总是会产生相同数量的项。
-
在一次迭代中出现的所有项目都将以相同的顺序出现在另一次迭代。
如果枚举没有产生有限数量的项,那么多次迭代是否产生相同数量的问题就没有意义了。然而,除此之外,IEnumerable<T>
的实现可以遵守上述标准的六个剩余组合中的任何一个:
-
许多集合,当只由一个线程使用时,当然会遵守所有三个线程。
-
一个"真正的"随机生成器可能不会遵守任何规则。
-
伪随机生成器可能仅遵守#3。
-
并发的仅添加列表可能遵循#1和#3。
-
其他一些并发集合可能只遵守#1。
-
并发代码使用的数组可能只遵守#1和#2。
尽管如果传递的对象未能遵守上述某些或全部特征,接收IEnumerable<T>
的一些方法可能会出现故障,但这些特征不是IEnumerable<T>
契约的一部分,IEnumerable<T>
也没有任何方法来指定它可以满足什么标准。我们的期望似乎是,将IEnumerable<T>
传递给方法的代码负责知道它有什么"类型"的IEnumerable<T>
,而将从外部代码接收到的IEnumerable<T>
传递给方法,方法应该以某种方式向其消费者传达IEnumerable<T>
实例所传递的方法的要求。
如果一个IEnumerable<T>
被枚举一次,它将以某种顺序返回项。对于大多数IEnumerable<T>
实现,重复的枚举将产生相同序列中的项,并且一些代码依赖于这种行为,但IEnumerable<T>
契约中没有指定这一点。