我正在查看一些调用扩展方法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
在两种情况下均相同)
答案 0 :(得分:6)
使用Linq表达式时,表达式的结果不会在表达式的定义中计算,而是在迭代时计算。
如果多次迭代,将计算结果(如果更改linq表达式中的基本列表,则结果可能不同。)
如果使用ToList()并保留方法的结果,结果将立即计算一次,并且当您对ToList()方法的结果进行多次迭代时,您肯定会有相同的结果输出
答案 1 :(得分:3)
“延迟执行”并不意味着结果集将在第二次调用时重用。它只是意味着在评估整个结果集之前可以返回第一个元素。
因此,如果您第二次迭代IEnumerable
(从OrderBy
返回),它将再次对集合进行排序(然后使用延迟执行来提供元素逐个排序的集合)。当你开始迭代时会发生这种排序,所以你是否消耗了所有元素并不重要。这适用于LINQ to Objects - SQL可能表现不同。
所以是的,因为记住“物化”结果集(来自ToArray
或ToList
)通常比重复排序更便宜,你应该这样做。在你的情况下,我猜测Multiply
迭代sorted
,并且被多次调用,所以当你避免多种排序时,你会发现很大的性能优势就不足为奇了。
注意:当谈到LINQ to SQL时,关系数据库具有“获取”的概念 - 它们能够在完成整个查询之前返回第一行,即使是在正确条件下进行排序(存在正确的索引)。在这种情况下,实际上,获取几个起始行可能比实现整个大结果集更便宜。
答案 2 :(得分:1)
要回答您的上一个问题,该方法的合同显示它是否可能使用延期执行。
当返回的接口为IEnumerable
或IQueryable
时,您应将其视为使用延迟执行。
当然,对于所有方法都不是这样,但是您应该根据方法的合同而不是实际的实现进行编程,因为实现可能会发生变化。
答案 3 :(得分:1)
而不是评论,我会调用&#34;排序&#34;变量&#34; localSortedList&#34; (如果它来自数据库)以表明它已被评估。
也许你可以在行上面加上一条评论,说#&#34; ToList()调用以防止多次迭代&#34;。
编辑: 为了回答原始问题,我会在多次使用它之前评估IEnumerable,假设它不是来自真正动态的数据源而你想要真正的最新数据。