为什么LINQ是不确定的?

时间:2018-11-17 06:55:43

标签: c# linq ienumerable deferred-execution non-deterministic

我随机排序了IEnumerable。我一直在打印相同的元素,并得到不同的结果。

string[] collection = {"Zero", "One", "Two", "Three", "Four"};
var random = new Random();
var enumerableCollection = collection.OrderBy(e => random.NextDouble());

Console.WriteLine(enumerableCollection.ElementAt(0));
Console.WriteLine(enumerableCollection.ElementAt(0));
Console.WriteLine(enumerableCollection.ElementAt(0));
Console.WriteLine(enumerableCollection.ElementAt(0));
Console.WriteLine(enumerableCollection.ElementAt(0));

每次写入都给出不同的随机元素。为什么不保留订单?

See it on .NET Fiddle

4 个答案:

答案 0 :(得分:5)

Linq将执行推迟到绝对必要为止。您可以将enumerableCollection视为要枚举工作的方式的定义,而不是单个枚举的结果

因此,每次枚举(调用ElementAt时所做的)都会重新枚举原始集合,并且由于您选择了随机订购,因此答案每次都不同。

您可以通过在末尾添加.ToList()使其达到预期的效果:

var enumerableCollection = collection.OrderBy(e => random.NextDouble()).ToList();

这将执行枚举,并将结果存储在列表中。通过使用此方法,您每次枚举enumerableCollection时都不会重新枚举原始列表。

答案 1 :(得分:4)

简单的答案是,您正在专门强制执行不确定性情况。 为什么是由于LINQ的功能。

LINQ的核心思想是构建对象,这些对象表示对一组数据(对象,记录等)的操作,然后在枚举时执行这些操作。当您调用a=A() a.price() print(a.name) 时,返回的对象将处理原点以执行排序,而不是已排序的新集合。顺序为仅当您实际枚举数据时-在这种情况下,通过调用OrderBy

每次枚举ElementAt对象时,它将运行您指定的操作序列,从而创建元素的随机顺序。因此,每个IEnumerable<string>调用都将返回新随机序列的第一个元素。

基本上,您可能会误解的是:ElementAt(0)不是集合

不过,您可以使用IEnumerable<T>IEnumerable<string>ToArray()转换为集合。这些将获取ToList()指定的操作序列的输出,并分别创建一个新集合-IEnumerable<>string[]

例如:

List<string>

现在,您将从原始数组中获得一个随机项,但是它将多次成为同一项。无需每次都随机化,而只是读取顺序随机的新集合中的第一项。

答案 2 :(得分:2)

许多以前的回答在技术上都是正确的,但我认为直接查看Enumerable的实现很有用。

我们可以看到ElementAt调用GetEnumerator,这反过来产生了当前分组的迭代。

如果您不熟悉,请参阅yield.

对于给定的示例,当前分组为

e => random.NextDouble()

这意味着我们将无休止地遍历集合(查看收益的无限循环),并为针对random.NextDouble()执行的每个元素返回分组的分辨率。

因此,这里的不确定性是由于随机分组的不确定性。这是LINQ终端语句(.List(),toArray()等)的预期行为,但是如果您尝试事先使用它们,可能会造成混淆。

答案 3 :(得分:1)

我很高兴问题在这里。尽管延迟执行的概念众所周知,但是人们仍然会犯错误并多次枚举IEnumerable,即使Eric Lippert在https://ericlippert.com/2014/10/20/producing-combinations-part-three/中也没有避免。这样做可能并最终破坏您的代码。如果您需要多次枚举IEnumerable,请实例化它(例如使用ToList)。

修改

在尊重埃里克·利珀特(Eric Lippert)权威的前提下,让我展示多重枚举的糟糕程度。这是基于列表的正确结果:

Zero,One,Two
Zero,One,Three
Zero,One,Four
Zero,Two,Three
Zero,Two,Four
Zero,Three,Four
One,Two,Three
One,Two,Four
One,Three,Four
Two,Three,Four

这是当我们处理顺序不稳定的序列时发生的事情:

Three,One,Four
Four,Two,One
One,Zero,Three
One,Zero,Four
Three,Two,Zero
One,Four,Zero
Two,Four,One
Two,Three,Four
Two,Three,Four
Four,Three,One

和要点

  
      枚举之一是对序列进行计数;一个序列       多次枚举时没有稳定计数的是       您不应该尝试将其组合成一个序列的序列!   
  1. 计数不一定会枚举序列。
  2.   
  3. 在该系列文章中,我强烈建议使用不可变的数据结构,该结构在枚举时总是安全且高效的   多次。
  4.   
  1. 我同意尝试将序列与不稳定计数组合在一起是有问题的。但这不是事实。序列具有计数和保证的项目,它们只是随机排序的。想象一下,它可能是由一些花哨的HashSet导致的,出于优化原因,这些HashSet可以在枚举之间重新排列其内部结构。
  2. 是的。那是可能的。但这真的是一个争论吗?
  3. 如果那是合同,好的。您期望可以根据需要进行多次枚举的序列,并且始终获得相同的结果。明确指出这样做可以避免混淆,并非每个序列都具有此属性。