请考虑以下代码
namespace ConsoleApp1
{
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main(string[] args)
{
int count = default(int);
IEnumerable<int> values1 = Enumerable.Range(1, 200)
.OrderBy(o => Guid.NewGuid())
.Take(100);
IEnumerable<int> values2 = values1
.OrderBy(o => Guid.NewGuid())
.Take(50)
.Select(o => { count++; return o; });
Console.Read();
}
}
}
重现的步骤
Console.Read();
count++
(应显示0)values2
并填充结果视图count++
(应显示100)问题
鉴于我只从values1
获取了50个项目,我希望count++
显示50个。为什么显示100个?
请注意,如果这令人困惑,请尝试运行此代码,它会产生相同的结果......
namespace ConsoleApp1
{
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main(string[] args)
{
int count = default(int);
IEnumerable<int> values1 = Enumerable.Range(1, 100)
.OrderBy(o => Guid.NewGuid())
.Take(50);
IEnumerable<int> values2 = values1
.OrderBy(o => Guid.NewGuid())
.Take(50)
.Select(o => { count++; return o; });
Console.Read();
}
}
}
示例
检查count++
检查values2
(填充结果视图)
检查count++
有关此处发生的事情以及如何解决问题的任何解释?
注意
许多给定的答案都表明延期执行。我知道linq使用延迟执行,所以除非我遗漏了什么,否则这不是问题。
我的观点是,当命中断点时,CLR为values2创建了一个状态机。然后在调试器中迭代,计数立即增加到100,看起来只有1次迭代。这看起来有点奇怪!
另外,我知道value2的结果视图的后续种群会导致计数增加,因为这会导致状态机的进一步迭代。
答案 0 :(得分:17)
每次检查values2
时,表达式都会再次进行评估 - 如果您在观察窗口中检查它,则每次都会计算两次(不要...问我为什么;问那些写手表窗口代码的人。我得到了count == 300
。每次评估某些内容时,都会将50
添加到count
;这是代码的作用,请亲自看看。每次在观察窗口中展开它时,count
都会增加100.因此,监视窗口会对其进行两次评估。
你只看到其中一次,但那又怎样? VS代码中有很多内容,它并不会给你带来麻烦。 GUI不是进入程序内部的窗口;它是屏幕上的一堆像素,有些代码故意上色。我可以编写一个观察窗口,评估表达式十九次并向您显示一个口袋妖怪。更合理的解释是什么:你从未见过的一些代码正在做一些没有碰巧在GUI中显示的东西,或者有时候你的计算机无法添加?< / p>
查看values2
的运行时类型:System.Linq.Enumerable.WhereSelectEnumerableIterator<int, int>
。没有收集,这是等待执行的东西。
让我们将ToList()
添加到该表达式的末尾。这将评估一次并存储结果。然后,您可以整天检查结果,而无需再次执行任何LINQ表达式。
int count = default(int);
IEnumerable<int> values1 = Enumerable.Range(1, 200)
.OrderBy(o => Guid.NewGuid())
.Take(100);
IEnumerable<int> values2 = values1
.OrderBy(o => Guid.NewGuid())
.Take(50)
.Select(o => { count++; return o; })
.ToList();
现在count == 50
,因为表达式仅评估一次,结果存储在List<T>
中。
屏幕上的点是幻觉,将懒惰的评价与副作用结合起来就像用机枪在星巴克放松猴子一样。我并不是说它错误,它并不是每个人都想要一个有趣的约会。
答案 1 :(得分:3)
因为Linq被执行为 deffered ,直到您显式调用ToList()
,或者迭代结果,才会调用委托。
当您在快速监视中查看投影的结果时,此时调用委托以将结果填充为@Ed也提及。