【问题标题】:To ToList() or not to ToList()?ToList() 还是不 ToList()?
【发布时间】:2015-05-01 10:06:21
【问题描述】:

给定一个内存中(不是 LINQ to SQL)类列表:

List<MyClass> myItems = /*lots and lots of items*/;

我使用GroupBy() 语句进行分组:

myItems.GroupBy(g => g.Ref)

然后立即在foreach 循环中使用,在“组”上调用.ToList() 是否有任何区别,或者我应该只使用IEnumerable

完整的代码示例:

ToList()

List<List<MyClass>> groupedItemsA = new List<List<MyClass>>();
List<List<MyClass>> groupedItemsB = new List<List<MyClass>>();

List<MyClass> myItems = /*lots and lots of items*/;
List<IGrouping<string, MyClass>> groupedItems = myItems.GroupBy(g => g.Ref).ToList();
foreach(IGrouping<string, MyClass> item in groupedItems)
{
  if (/*check something*/)
  {
     groupedItemsA.Add(item.ToList());
  }
  else
  {
    groupedItemsB.Add(item.ToList());
  }
}

使用IEnumerable

List<List<MyClass>> groupedItemsA = new List<List<MyClass>>();
List<List<MyClass>> groupedItemsB = new List<List<MyClass>>();


List<MyClass> myItems = /*lots and lots of items*/;
IEnumerable<IGrouping<string, MyClass>> groupedItems = myItems.GroupBy(g => g.Ref);
foreach(IGrouping<string, MyClass> item in groupedItems)
{
  if (/*check something*/)
  {
     groupedItemsA.Add(item.ToList());
  }
  else
  {
    groupedItemsB.Add(item.ToList());
  }
}

这些“幕后”的执行计划有什么不同吗?这些中的任何一个会更有效还是真的不重要?

在此之后使用groupedItems 列表。

【问题讨论】:

  • 要执行查询时使用 ToList()。当您的可枚举(以及因此您的查询)在代码中的稍后点(例如通过另一个 where 条件)扩展时,不要使用 ToList()。
  • 为什么不按 /*check something*/ 中的条件分组?
  • “检查某事”依赖于分组。
  • 我认为你的两个例子都有一个类型,应该包含foreach(IGrouping&lt;string, MyClass&gt; item in groupedItems)
  • 是的,剪切和粘贴错误@Hogan

标签: c# performance linq ienumerable


【解决方案1】:

是的,这是有区别的,而且可能很重要。

ToList() 将迭代并将每个迭代的项目附加到一个新列表中。这具有创建消耗内存的临时列表的效果。

有时您可能想要承担内存损失,特别是如果您打算多次迭代列表并且原始列表不在内存中。

在您使用 ToList() 的特定示例中,您实际上最终迭代了两次 - 一次是构建列表,第二次是在您的 foreach 中。根据列表的大小和您的应用程序,这可能是也可能不是问题。

【讨论】:

  • 这就是我认为可能发生的事情。您可以添加对此的任何引用来备份它吗?我很难找到任何可以证明这就是正在发生的事情的东西?
  • 这是 ToList() 的隐含行为。它将需要强制评估查询以构建列表 - 没有其他方法可以解决它。见msdn.microsoft.com/en-us/library/vstudio/…的备注部分
【解决方案2】:

如果您确定您将只使用groupedItems一次,那么使用.ToList() 有一个优势:如果有异常(例如因为您的代码在分组期间为计算 .Ref) 做了一些有趣的事情,例外将在 .ToList() 行中,而不是在 foreach 内...我不认为这是一个很大的优势(也许这是一个缺点)。

澄清一下:

public class MyClass
{
    public string Ref
    {
        get
        {
            // sometimes I like to throw an exception!
            if (DateTime.Now.Ticks % 10 == 0) throw new Exception();

            return "Foo";
        }
    }
}

请注意,您已将此问题明确标记为IEnumerable,并且在您的示例中myItemsList&lt;&gt;,因此我不会讨论在您读取数据时是否执行ToList() 的区别从数据库通过 Entity Framework/Linq 到 SQL。

【讨论】:

  • 我正在寻找您在使用实体框架时对 ToList 的评论;)
  • @Stefan 在 EF 上使用 ToList() 可以强制在 ToList() 点执行查询。 ToList() 之后的所有内容都在 .NET 端而不是 SQL 端执行。
  • 在 EF 上,Single()First() 是否执行查询?
  • @Stefan 是的,甚至还有 ToArray()
【解决方案3】:

有什么不同吗?

是的,.ToList() 通过迭代分组集合创建一个新列表:

The ToList&lt;TSource&gt;(IEnumerable&lt;TSource&gt;) method forces immediate query evaluation and returns a List that contains the query results

这是否明显应该由您进行基准测试。

如果只迭代一次分组集合,.ToList() 步骤是不必要的,并且会比直接枚举GroupBy() 结果相对慢。

【讨论】:

    【解决方案4】:

    如果您每次枚举时都使用IEnumerable 而不是List(),则GroupBy 表达式将根据您的myItems 列表重新评估。

    这意味着如果您向myItems 添加另一个项目然后枚举它,该项目将包含在GroupBy 表达式中。

    当您调用ToList 时,您会创建一个新列表,并且对myItems 的任何更改都不会包含在groupedItems 中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多