为什么IEnumerable慢,List快?

碰到了这段代码。

var dic = new Dictionary(); for(int i=0; i 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()被评论时它很慢,当没有时 – 它很快。 这里可再现如何解释? 我是否应该始终制作所有ToList()以确保速度(即在哪种情况下IEnumerable会更优选)? 注意我只是谈论对象的linq,我知道linq对sql懒惰和东西。

这是因为延迟执行 :当您注释掉ToList ,通过评估字典中每个项目的filter序列来生成枚举。 但是,当您执行ToList时,序列在内存中“物化”,因此所有评估只执行一次。

第二个没有ToList背后的逻辑如下所示:

 // The logic is expanded for illustration only. var list2 = new List>(); foreach (var d in dict) { var list = new List(); // 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(); foreach (var f in dict) { if (f.Value.StartsWith("1")) { list.Add(f.Key); } } var list2 = new List>(); // This loop uses the same list in all its iterations. foreach (var d in dict) { if (list.Contains(d.Key)) { list2.Add(d); } } 

如您所见, ToList将带有两个大小为n嵌套循环的O(n^2)程序转换为O(2*n) ,每个循环的大小为n

LINQ使用延迟执行
除非你调用.ToList() ,否则查询的结果永远不会存储在任何地方; 相反,它会在每次迭代结果时重新迭代查询。

通常,这要快得多; 通常没有理由先将所有结果存储在内存中。

但是,您的代码会重复迭代查询; 每次调用Where()回调一次。

您应该使用Join()调用替换该行,而不是使用ToList() ,这将比任何一种方法都快。

因为当您没有.ToList()调用时, list2实例化将遍历字典中每个项目的可枚举的整个list 。 因此,如果使用延迟执行,则从O(n)到O(n ^ 2)。

它是由延迟执行引起的。 IEnumerable不一定是静态集合。 通常它是一些数据源(在你的情况下是dic )+所有导致最终集的方法和表达式(Where,Contains等)。

.ToList()执行所有这些方法和表达式并生成最终结果。

因此,如果您使用ToList(),它会生成一个标准的.NET List(整数数组),并对该列表执行所有操作。

如果不调用ToList()(或任何其他To方法),可以多次枚举IEnumerable。