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

时间:2016-08-04 13:37:12

标签: 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();
        }
    }
}

重现的步骤

  1. Console.Read();
  2. 上设置断点
  3. 运行到断点
  4. 检查count++(应显示0)
  5. 检查values2并填充结果视图
  6. 检查count++(应显示100)
  7. 问题

    鉴于我只从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++

    enter image description here

    检查values2(填充结果视图)

    enter image description here

    检查count++

    enter image description here

    有关此处发生的事情以及如何解决问题的任何解释?

    注意

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

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

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

2 个答案:

答案 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也提及。