遇到此代码。
var dic = new Dictionary<int, string>();
for(int i=0; i<20000; i++)
{
dic.Add(i, i.ToString());
}
var list = dic.Where(f => f.Value.StartsWith("1")).Select(f => f.Key);//.ToList(); //uncomment for fast results
Console.WriteLine(list.GetType());
var list2 = dic.Where(f => list.Contains(f.Key)).ToList();
Console.WriteLine(list2.Count())
所以当.ToList()被评论时它很慢,当没有时 - 它很快。可再生here如何解释?我是否应该始终制作ToList()以确保速度(即在哪种情况下IEnumerable更可取)?注意我只谈论linq到对象,我知道linq对sql laziness和stuff。
答案 0 :(得分:23)
这是因为延迟执行:当您注释掉ToList
时,枚举是通过评估字典中每个项目的过滤器序列来生成的。但是,当您执行ToList
时,序列在内存中“具体化”,因此所有评估都只执行一次。
没有Where
的第二个ToList
背后的逻辑如下:
// The logic is expanded for illustration only.
var list2 = new List<KeyValuePair<int,string>>();
foreach (var d in dict) {
var list = new List<int>();
// This nested loop does the same thing on each iteration,
// redoing n times what could have been done only once.
foreach (var f in dict) {
if (f.Value.StartsWith("1")) {
list.Add(f.Key);
}
}
if (list.Contains(d.Key)) {
list2.Add(d);
}
}
ToList
的逻辑如下所示:
// The list is prepared once, and left alone
var list = new List<int>();
foreach (var f in dict) {
if (f.Value.StartsWith("1")) {
list.Add(f.Key);
}
}
var list2 = new List<KeyValuePair<int,string>>();
// This loop uses the same list in all its iterations.
foreach (var d in dict) {
if (list.Contains(d.Key)) {
list2.Add(d);
}
}
正如您所看到的,ToList
将O(n^2)
程序与两个大小为n
的嵌套循环转换为O(2*n)
,其中包含两个大小为n
的连续循环每个
答案 1 :(得分:12)
LINQ使用延迟执行
除非您致电.ToList()
,否则查询结果永远不会存储在任何地方;相反,它会在每次迭代结果时重新迭代查询。
通常,这要快得多;通常没有理由将所有结果存储在内存中。
但是,您的代码会反复迭代查询;每次拨打Where()
回拨一次。
您应该使用Join()
调用替换该行,而不是ToList()
,这将比任何一种方法都快。
答案 2 :(得分:2)
这是由延迟执行引起的。
IEnumerable不必是静态集合。通常它是一些数据源(在您的情况下为dic
)+所有导致最终集的方法和表达式(Where,Contains等)。
.ToList()执行所有这些方法和表达式并生成最终结果。
因此,如果您使用ToList(),它会生成一个标准的.NET List(整数数组)并对该列表执行所有操作。
如果你没有调用ToList()(或任何其他To方法),可以多次枚举IEnumerable。
答案 3 :(得分:2)
因为当您没有.ToList()
调用时,list2
实例化将遍历字典中每个项目的整个list
枚举。因此,如果使用延迟执行,则从O(n)到O(n ^ 2)。