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()这样会导致迭代两次。 操作顺序如下(为了简化,我将假设所有项都符合条件):

  1. 调用Where() ,现在没有迭代, Where()返回一个特殊的可枚举。
  2. 调用ToList() ,在Where()返回的枚举上调用MoveNext() Where()
  3. Where()现在调用原始集合上的MoveNext()并获取值。
  4. Where()调用您的谓词,返回true
  5. ToList() MoveNext()返回, ToList()获取值并将其添加到列表中。

这意味着如果原始集合中的所有n个项都与条件匹配,则MoveNext()将被调用2次,从Where()调用n次,从ToList()调用nToList()

 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。 使其成为元素数量+列表末尾。

首先, 你为什么要问我? 衡量你自己,看看。

也就是说, WhereSelectOrderBy和其他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会在内部设置一个状态机,用于跟踪进程在迭代中的位置。 哪里不进行完整迭代创建临时列表,然后将这些结果传递给查询的其余部分。 它只是确定一个项目是否符合标准,只传递匹配的项目。