C# 为什么LINQ是非确定性的?
我对一个C# 为什么LINQ是非确定性的?,c#,linq,ienumerable,deferred-execution,non-deterministic,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.E
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));
每次写入都会产生不同的随机元素。为何不维持秩序
前面的许多回答在技术上都是正确的,但我认为直接查看应用程序的实现是有用的 我们可以看到这些调用,而这些调用又会在当前分组的迭代中产生 如果您不熟悉Yeld,请参阅 对于给定的示例,当前分组为
e => random.NextDouble()
这意味着我们在集合上无休止地迭代(查看yield的inifinite循环),并返回针对random.NextDouble()执行的每个元素的分组分辨率
因此,这里的不确定性是由于随机分组的不确定性。这是LINQ terminal语句(.List()、toArray()等)的预期行为,但如果事先尝试使用它们,则可能会造成混乱。LINQ会将执行延迟到绝对必要时。您可以将
enumerableCollection
视为希望枚举如何工作的定义,而不是单个枚举的结果
因此,每次您枚举它时(您在调用ElementAt
时正在执行此操作),它都会重新枚举您的原始集合,并且由于您选择随机排序,每次的答案都不同
通过在末尾添加.ToList()
,您可以使其达到预期效果:
var enumerableCollection = collection.OrderBy(e => random.NextDouble()).ToList();
这将执行枚举,并将结果存储在列表中。通过使用此选项,不会在每次枚举
enumerableCollection
时重新枚举原始列表。简单的答案是,您具体地强制了一种不确定的情况。原因在于LINQ的工作方式
LINQ的核心是围绕构建表示一组数据上的操作的对象(对象、记录等)的概念而设计的,然后在枚举时执行这些操作。调用OrderBy
时,返回的是一个对象,它将处理原始数据以执行排序,而不是一个已排序的新集合。只有当您实际枚举数据时,才有顺序—在本例中,通过调用ElementAt
每次枚举IEnumerable
对象时,它都会运行指定的操作序列,创建元素的随机顺序。因此,每个ElementAt(0)
调用将返回新随机序列的第一个元素
基本上,您似乎误解了这一点:IEnumerable
不是集合
但是,您可以使用ToArray()
或ToList()
将IEnumerable
转换为集合。它们将获取由IEnumerable
指定的操作序列的输出,并分别创建一个新集合-字符串[]
或列表
例如:
string[] collection = {"Zero", "One", "Two", "Three", "Four"};
var random = new Random();
var enumerableCollection = collection.OrderBy(e => random.NextDouble()).ToArray();
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));
现在,您将从原始数组中获得一个随机项,但它将多次成为同一项。它不是每次随机阅读,而是阅读顺序随机的新集合中的第一项。我很高兴问题就在这里。虽然延迟执行的概念众所周知,但人们仍然会犯错误并多次枚举IEnumerable
,即使Eric Lippert在年也没有避免这一点。这样做可能最终会破坏您的代码。如果需要多次枚举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
切中要害
其中一个枚举是对序列进行计数;序列
在多次枚举时没有稳定计数的
您不应该尝试组合的序列李>
计数不一定枚举序列李>
在该系列文章中,我强烈鼓励使用不可变的数据结构,这些结构在枚举时总是安全且性能良好的
多次
我同意,试图用不稳定计数组合序列是有问题的。但事实并非如此。序列已经给出了计数和保证项,它们只是随机排序的。想象一下,这可能是某种奇特的哈希集的结果,该哈希集可以出于优化原因在枚举之间重新排列其内部结构
这是真的。这种可能性是存在的。但这真的是一个论点吗
如果那是合同,好的。您希望序列可以根据需要多次枚举,并且总是得到相同的结果。明确地说可以避免混淆,并不是每个序列都有这个属性
为什么每次都希望它返回相同的值?要理解的关键问题可能是LINQ查询在实现之前不会做任何事情(例如,ToList
,ToArray
,foreach
,ElementAt
)。您没有执行一次查询然后获取第一个元素。你要把它具体化五次。所以每次,它的顺序都不同——就像你告诉它的那样。@Evorlor阅读:“此方法通过使用延迟执行来实现。立即返回值是存储执行操作所需的所有信息的对象。通过直接调用对象的GetEnumerator方法或使用foreach.枚举对象之前,不会执行此方法表示的查询。重用该IEnumerable
(例如,通过使用.ElementAt(0)将其具体化