Linq中的ToList方法
如果我没有错,ToList()方法将迭代所提供集合的每个元素,并将它们添加到List的新实例并返回此实例。假设示例
//using linq list = Students.Where(s => s.Name == "ABC").ToList(); //traditional way foreach (var student in Students) { if (student.Name == "ABC") list.Add(student); }
我认为传统的方法更快,因为它只循环一次,其中Linq的上面迭代两次一次用于Where方法然后用于ToList()方法。
我正在研究的项目现在已广泛使用Lists,我发现如果我将list变量作为IEnumerable并删除.ToList,有很多类似的使用ToList()和其他方法,可以像上面那样做得更好。 ()并将其作为IEnumerable进一步使用。
这些事情会对性能产生什么影响吗?
这些事情会对性能产生什么影响吗?
这取决于你的代码。 大多数情况下,使用LINQ确实会导致性能下降。 在某些情况下,这个命中对您来说可能很重要,但是只有当您知道它对您来说太慢时才应该避免LINQ(即,如果对代码进行分析表明LINQ是您的代码速度慢的原因)。
但是你经常使用ToList()
会导致严重的性能问题。 只有在必要时才应调用ToList()
。 请注意,在某些情况下,添加ToList()
可以ToList()
提高性能(例如,每次迭代时从数据库加载集合时)。
关于迭代次数:它取决于“迭代两次”究竟是什么意思。 如果计算在某些集合上MoveNext()
的次数,那么是的,使用Where()
这样会导致迭代两次。 操作顺序如下(为了简化,我将假设所有项都符合条件):
- 调用
Where()
,现在没有迭代,Where()
返回一个特殊的可枚举。 - 调用
ToList()
,在Where()
返回的枚举上调用MoveNext()
Where()
。 -
Where()
现在调用原始集合上的MoveNext()
并获取值。 -
Where()
调用您的谓词,返回true
。 - 从
ToList()
MoveNext()
返回,ToList()
获取值并将其添加到列表中。 - …
这意味着如果原始集合中的所有n个项都与条件匹配,则MoveNext()
将被调用2次,从Where()
调用n次,从ToList()
调用n次ToList()
。
var list = Students.Where(s=>s.Name == "ABC");
这将只创建一个查询,而不是循环元素,直到使用查询。 通过调用ToList()将首先执行查询,因此只循环您的元素一次。
List studentList = new List (); var list = Students.Where(s=>s.Name == "ABC"); foreach(Student s in list) { studentList.add(s); }
这个例子也只会迭代一次。 因为它只用过一次。 请记住,列表将在每次调用时迭代所有学生。不仅仅是名称为ABC的学生。 既然是一个查询。
对于后来的讨论,我做了一个例子。 也许它不是IEnumable的最佳实现,但它做了它应该做的事情。
首先我们有我们的清单
public class TestList : IEnumerable { private TestEnumerator _Enumerator; public TestList() { _Enumerator = new TestEnumerator (); } public IEnumerator GetEnumerator() { return _Enumerator; } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw new NotImplementedException(); } internal void Add(T p) { _Enumerator.Add(p); } }
而且由于我们想要计算调用MoveNext的次数,我们必须实现我们的自定义枚举器aswel。 在MoveNext中观察我们的程序中有一个静态的计数器。
public class TestEnumerator:IEnumerator {public Item FirstItem = null; public Item CurrentItem = null;
public TestEnumerator() { } public T Current { get { return CurrentItem.Value; } } public void Dispose() { } object System.Collections.IEnumerator.Current { get { throw new NotImplementedException(); } } public bool MoveNext() { Program.Counter++; if (CurrentItem == null) { CurrentItem = FirstItem; return true; } if (CurrentItem != null && CurrentItem.NextItem != null) { CurrentItem = CurrentItem.NextItem; return true; } return false; } public void Reset() { CurrentItem = null; } internal void Add(T p) { if (FirstItem == null) { FirstItem = new Item(p); return; } Item lastItem = FirstItem; while (lastItem.NextItem != null) { lastItem = lastItem.NextItem; } lastItem.NextItem = new Item (p); } }
然后我们有一个自定义项目,只包含我们的价值
public class Item { public Item(T item) { Value = item; } public T Value; public Item NextItem; }
要使用实际代码,我们创建一个包含3个条目的“列表”。
public static int Counter = 0; static void Main(string[] args) { TestList list = new TestList (); list.Add(1); list.Add(2); list.Add(3); var v = list.Where(c => c == 2).ToList(); //will use movenext 4 times var v = list.Where(c => true).ToList(); //will also use movenext 4 times List tmpList = new List (); //And the loop in OP question foreach(var i in list) { tmpList.Add(i); } //Also 4 times. }
结论呢? 它如何影响性能? 在这种情况下,MoveNext被称为n + 1次。 无论我们有多少件物品。 而WhereClause也没关系,他仍然会运行4次MoveNext。 因为我们总是在初始列表上运行查询。 我们将采取的唯一性能影响是实际的LINQ框架及其调用。 实际的循环将是相同的。
之前有人问为什么N + 1次而不是N次。 这是因为他最后一次失去元素时会返回false。 使其成为元素数量+列表末尾。
首先, 你为什么要问我? 衡量你自己,看看。
也就是说, Where
, Select
, OrderBy
和其他LINQ IEnumerable
扩展方法通常实现为尽可能惰性(经常使用yield
关键字)。 这意味着除非必须,否则他们不会处理数据。 从你的例子:
var list = Students.Where(s => s.Name == "ABC");
不会执行任何事情。 即使Students
是1000万个对象的列表,这也会立即返回。 在某个地方实际请求结果之前,根本不会调用谓词,这实际上就是ToList()
作用:它表示“是的,结果 – 所有这些都是立即需要的”。
但是,在调用LINQ方法时会有一些初始开销,因此传统方式通常会更快,但可组合性和LINQ方法的易用性,恕我直言,不仅仅是对此进行补偿。
如果您想了解这些方法是如何实现的,可以从Microsoft Reference Sources中获取它们。
要完全回答这个问题,这取决于实施。 如果你在谈论LINQ to SQL / EF,那么在这种情况下只有一次迭代,当调用.ToList时,内部调用.GetEnumerator。 然后将查询表达式解析为TSQL并传递给数据库。 然后迭代(一次)生成的行并将其添加到列表中。
在LINQ to Objects的情况下,也只有一次通过数据。 在where子句中使用yield return会在内部设置一个状态机,用于跟踪进程在迭代中的位置。 哪里不进行完整迭代创建临时列表,然后将这些结果传递给查询的其余部分。 它只是确定一个项目是否符合标准,只传递匹配的项目。