如果多次使用,是否应该对从OrderBy返回的IEnumerable进行评估?

Too*_*one 2 .net c# linq sorting

我正在看一些调用扩展方法的代码OrderBy.结果IEnumerable可能会多次使用.我已经听说过使用LINQ,如果表达式可能被多次使用,那么最好对它们进行评估,因为如果不这样做,LINQ查询将被执行多次.这也是这种情况吗?(最初,查看代码,我没有意识到它是LINQ,但我从MSDN文档中看到OrderBy了LINQ命名空间.)

为了使其具体化,代码看起来像这样,除了枚举的项目比a更复杂,int并且可能比这个简单示例中的更多数量级.

IEnumerable<int> Multiply(IEnumerable<int> list, int howMany, int k)
{
    return list.Take(howMany).Select(i => i * k);
}

void Main()
{
    int[] unsorted = { 1, 7, 3, 9, 4 };
    IEnumerable<int> sorted = unsorted.OrderBy(i=>i); // Add .ToList() ?
    for(int k=1; k<=3; ++k) {
        IEnumerable<int> multiplied = Multiply(sorted, k, k);
        Console.WriteLine(String.Join(", ", multiplied));
    }
}
Run Code Online (Sandbox Code Playgroud)

无论我是否使用,此代码都具有相同的输出.ToList().

1
2, 6
3, 9, 12
Run Code Online (Sandbox Code Playgroud)

看起来有点令人惊讶的是,这段代码可能会反复排序.但如果它是,而且我应该有.ToList(),那么输出是相同的,那么一般来说,我应该知道这.ToList()是必需的吗?它只是看到了神奇的话语

延期执行

在文档中?


为了解决@Matt Burland的建议,我应该为自己测试性能,我将程序改为以下(使用doubles来避免溢出问题).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;

namespace OrderByPerformanceTest
{
    class Program
    {
        static IEnumerable<double> Multiply(IEnumerable<double> list, int howMany, double k)
        {
            return list.Take(howMany).Select(i => i * k);
        }

        static void Main(string[] args)
        {
            int n = 1000;
            IEnumerable<double> unsorted = Enumerable.Range(0, n).Select(i => (double)(n-i));
            //Console.WriteLine(String.Join(", ", unsorted));
            IEnumerable<double> sorted1 = unsorted.OrderBy(i => i); // Add .ToList() ?
            //Console.WriteLine(String.Join(", ", sorted1));
            var sw = new Stopwatch();
            sw.Start();
            double sum = 0;
            for (int k = 1; k <= n; ++k)
            {
                IEnumerable<double> multiplied = Multiply(sorted1, k, k);
                sum += multiplied.Sum();
                //Console.WriteLine(String.Join(", ", multiplied));
            }
            sw.Stop();
            Console.WriteLine("Time {0}ms, sum {1}", sw.ElapsedMilliseconds, sum);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

结果:

  • 没有ToList,115毫秒
  • ToList,10ms

(sum两种情况都相同)

Seu*_*euh 6

使用Linq表达式时,表达式的结果不会在表达式的定义中计算,而是在迭代时计算.

如果多次迭代,将计算结果(如果更改linq表达式中的基本列表,则结果可能不同).

如果你使用ToList()并保留方法的结果,结果将立即计算,只有一次,当你在ToList()方法的结果上多次迭代时,你肯定会有相同的输出.