请考虑以下代码
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()上放置一个断点
>运行到断点
>检查次数(应显示0)
>检查值2并填充“结果视图”
>检查数(应显示100)
问题
鉴于我仅从值1中获取了50个项目,我期望计数显示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(); } } }
例
检查次数
检查值2(填充结果视图)
检查次数
关于这里发生了什么的解释,以及如何解决?
注意
许多给定的答案表明延期执行.我知道linq使用延期执行,所以除非我错过了一些事情,这不是问题.
我的观点是当断点被击中时,CLR已创建一个状态机为值2.然后在调试器中迭代,计数立即增加到100,看来只有1次迭代.这似乎有点奇怪!
解决方法
每次检查值2时,表达式将再次进行评估,如果您在监视窗口中检查该表达式,则每次都会进行两次评估(不要问我为什么;询问编写监视窗口代码的人员).我得到了count == 300.每当有一些评估它,它增加50计数;这就是代码所做的,看看你自己.并且每次在观察窗口中展开它,计数增加100.所以,观察窗口评估它两次.
你只看到那些时代,但是呢?许多东西都在VS代码里面,它不麻烦给你看. GUI不是进入程序内部的窗口;屏幕上有一些像素,一些代码有意识地着色.我可以编写一个监视窗口,评估表达十九次,并显示一个口袋妖怪.什么是更合理的解释:你从未见过的一些代码是做一些不会在GUI中显示的东西,或者有时您的计算机无法添加?
查看值2的运行时类型: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>中.
故事的道德启示:
屏幕上的点是幻觉,结合懒惰评估与副作用就像用机枪在星巴克放松猴子.我不是说这是错的,只是不是每个人都有一个有趣的日子.