C#LINQ评估顺序(从右到左)?

use*_*555 -1 c# linq

LINQ从右到左评估子句?这就是为什么看起来有太多文章最后使用Take操作解释“惰性评估”的原因?下面的示例代码段2比代码段1快得多,因为它没有执行“ ToList”

代码段1(约13000毫秒)

        var lotsOfNums = Enumerable.Range(0, 10000000).ToList();

        Stopwatch sw = new Stopwatch();
        sw.Start();

        // Get all the even numbers
        var a = lotsOfNums.Where(num => num % 2 == 0).ToList();

        // Multiply each even number by 100.
        var b = a.Select(num => num * 100).ToList();

        var c = b.Select(num => new Random(num).NextDouble()).ToList();

        // Get the top 10
        var d = c.Take(10);

        // a, b, c and d have executed on each step.
        foreach (var num in d)
        {
            Console.WriteLine(num);
        }

        sw.Stop();
        Console.WriteLine("Elapsed milliseconds: " + sw.ElapsedMilliseconds);
Run Code Online (Sandbox Code Playgroud)

代码段2(3毫秒)

        sw.Reset();
        sw.Start();

        var e = lotsOfNums.Where(num => num % 2 == 0).Select(num => num * 100).Select(num => new Random(num).NextDouble()).Take(10);
        foreach (var num in e)
        {
            Console.WriteLine(num);
        }
        sw.Stop();
        Console.WriteLine("Elapsed milliseconds: " + sw.ElapsedMilliseconds);
        Console.Read();
Run Code Online (Sandbox Code Playgroud)

但是,对于代码片段2,我发现“ Take”的相对位置不相关吗?

具体来说,我更改为:var e = lotsOfNums.Where(num => num%2 == 0).Select(num => num * 100).Select(num =>新的Random(num).NextDouble() )。采取(10);

至:

   var e = lotsOfNums.Take(10).Where(num => num % 2 == 0).Select(num => num * 100).Select(num => new Random(num).NextDouble());
Run Code Online (Sandbox Code Playgroud)

性能没有区别吗?

同样值得注意的是,如果将NextDouble移到最右边,由于LINQ从左到右求值,结果列表将为空,并且Select(NextDouble)会强制左侧的所有后续子句遍历整个列表,这将花费更长的时间该进行评估了。

  var e = lotsOfNums.Select(num => new Random(num).NextDouble()).Where(num => num % 2 == 0).Select(num => num * 100).Take(10);
Run Code Online (Sandbox Code Playgroud)

Eri*_*ert 8

LINQ从右到左评估子句?

不,子句从左到右求值。一切都在C#中从左到右进行评估。

这就是为什么看起来有太多文章最后使用Take操作解释“惰性评估”的原因?

我不明白这个问题。


更新:我理解这个问题。原始发布者错误地认为Take具有ToList的语义;它执行查询,因此结束。这种信念是不正确的。Take子句只是将Take操作附加到查询中;它不执行查询。


您必须将Take操作放在需要的地方。请记住,x.Take(y).Where(z)并且x.Where(z).Take(y)有很大的不同的查询。您不能只Take在不改变查询含义的情况下四处走动,因此请将其放在正确的位置:尽早,但不要过早地改变查询的含义。

“ NextDouble”选择子句的位置有关系吗?

对谁有关系?再说一次,我不明白这个问题。你能澄清一下吗?

为什么codenippet 1和codenippet 2具有相同的性能统计信息?

由于您没有提供给我们您的测量结果,因此我们没有进行比较的依据。但是您的两个代码示例做的事情完全不同。一个执行一个查询,一个仅创建一个查询。建立一个从未执行过的查询比执行它要快!

我以为“ ToList”会强制早期评估从而使事情变慢?

没错

性能没有区别吗?(在我的两个查询构造之间)

您已经构造了两个查询;您尚未执行它们。查询的构建速度很快,通常不值得测量。如果您想知道查询执行的速度,请衡量查询执行的性能,而不是查询的构造!