LINQ投影(选择)返回奇数结果

ser*_*0ne 9 c# linq

请考虑以下代码

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

重现步骤

  1. 断点 Console.Read();
  2. 跑到断点
  3. 检查count++(应显示0)
  4. 检查values2并填充结果视图
  5. 检查count++(应显示100)

问题

鉴于我只拍摄了50个项目values1,我希望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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

检查 count++

在此输入图像描述

检查values2(填充结果视图)

在此输入图像描述

检查 count++

在此输入图像描述

关于这里发生了什么,以及如何解决它的任何解释?

注意

许多给出的答案表明延期执行.我知道linq使用延迟执行,所以除非我遗漏了什么,否则这不是问题.

我的观点是,当命中断点时,CLR为values2创建了一个状态机.然后在调试器中迭代,计数立即增加到100,看起来只有1次迭代.这看起来有点奇怪!

此外,我知道value2的结果视图的后续群体会导致计数增加,因为这会导致状态机的进一步迭代.

15e*_*153 17

每次检查时values2,表达式都会再次进行评估 - 如果你在监视窗口中检查它,它似乎每次都会被评估两次(不要问我原因;请问那些编写监视窗口代码的人).我有count == 300.每当某计算它,把它添加50count; 这就是代码的作用,亲自看看.每次在监视窗口中展开它时,都会count增加100.因此,监视窗口会对其进行两次评估.

你只看到其中一个时代,但那又怎么样?很多东西都在VS代码里面进行,它没有给你看.GUI不是进入程序内部的窗口; 它是屏幕上的一堆像素,有些代码故意着色.我可以编写一个观察窗口,评估表达式十九次并向您显示一个口袋妖怪.更合理的解释是什么:你从未见过的一些代码正在做一些不会在GUI中显示的内容,或者有时你的计算机无法添加?

查看运行时类型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();
Run Code Online (Sandbox Code Playgroud)

现在count == 50,因为表达式只被评估一次,结果存储在一个List<T>.

故事的道德启示:

屏幕上的圆点是幻觉,将懒惰的评价与副作用结合起来就像用机枪在星巴克放松猴子一样.我不是说这是错的,只是不是每个人都想到一个有趣的约会.

  • @ series0ne关于评估数量的唯一准确信息是`count`的值,它告诉你当天有***评价.你确切知道你在做什么来"数"; 这是绝对已知的确定性.观察窗的内部是一个疯狂的猜测.是的,它不会*看起来像你列举的那样两次隔离; 你没有*看到*手表窗口那样做; 但为什么要在你能看到的地方做到这一点呢?这是代码.它可以做各种各样的事情,UI只显示它向你显示的内容. (2认同)