为什么IEnumerable慢而List很快?

时间:2013-10-30 17:05:20

标签: c# linq

遇到此代码。

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。

4 个答案:

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

正如您所看到的,ToListO(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)。