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

时间:2014-06-27 15:52:21

标签: c# .net linq sorting

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

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

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));
    }
}

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

1
2, 6
3, 9, 12

这段代码可能会反复排序,这似乎有点令人惊讶。但是如果它是,并且我应该有.ToList(),那么输出是相同的,所以我一般应该知道.ToList()是否需要?它只是看到了神奇的话语

  

延期执行

在文档中?


要解决@Matt Burland建议我应该自己测试性能,我将程序更改为以下内容(使用double来避免溢出问题。)

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);
        }
    }
}

结果:

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

sum在两种情况下均相同)

4 个答案:

答案 0 :(得分:6)

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

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

如果使用ToList()并保留方法的结果,结果将立即计算一次,并且当您对ToList()方法的结果进行多次迭代时,您肯定会有相同的结果输出

答案 1 :(得分:3)

“延迟执行”并不意味着结果集将在第二次调用时重用。它只是意味着在评估整个结果集之前可以返回第一个元素。

因此,如果您第二次迭代IEnumerable(从OrderBy返回),它将再次对集合进行排序(然后使用延迟执行来提供元素逐个排序的集合)。当你开始迭代时会发生这种排序,所以你是否消耗了所有元素并不重要。这适用于LINQ to Objects - SQL可能表现不同。

所以是的,因为记住“物化”结果集(来自ToArrayToList)通常比重复排序更便宜,你应该这样做。在你的情况下,我猜测Multiply迭代sorted,并且被多次调用,所以当你避免多种排序时,你会发现很大的性能优势就不足为奇了。


注意:当谈到LINQ to SQL时,关系数据库具有“获取”的概念 - 它们能够在完成整个查询之前返回第一行,即使是在正确条件下进行排序(存在正确的索引)。在这种情况下,实际上,获取几个起始行可能比实现整个大结果集更便宜。

答案 2 :(得分:1)

要回答您的上一个问题,该方法的合同显示它是否可能使用延期执行。

当返回的接口为IEnumerableIQueryable时,您应将其视为使用延迟执行。

当然,对于所有方法都不是这样,但是您应该根据方法的合同而不是实际的实现进行编程,因为实现可能会发生变化。

答案 3 :(得分:1)

而不是评论,我会调用&#34;排序&#34;变量&#34; localSortedList&#34; (如果它来自数据库)以表明它已被评估。

也许你可以在行上面加上一条评论,说#&#34; ToList()调用以防止多次迭代&#34;。

编辑: 为了回答原始问题,我会在多次使用它之前评估IEnumerable,假设它不是来自真正动态的数据源而你想要真正的最新数据。