【问题标题】:Performance of IEnumerable [duplicate]IEnumerable 的性能 [重复]
【发布时间】:2019-01-03 05:09:49
【问题描述】:

我有这个代码

List<int> items = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
//calculation is not being executed until "even" is used
IEnumerable<int> even = items.Where(x => x % 2 == 0);   

DoStuff1(even);
DoStuff2(even);
DoStuff3(even);

我已阅读此答案https://stackoverflow.com/a/3628705/6887468 它说

当您使用 IEnumerable 时,您可以让编译器有机会将工作推迟到以后进行,可能会在此过程中进行优化。如果您使用 ToList(),您会强制编译器立即具体化结果。

现在是在每次调用DoStuff() 时执行此计算(在我的示例中为x % 2 == 0)还是以某种方式保存在内存中?

【问题讨论】:

  • 这取决于DoStuff的代码!
  • 假设您在 where.IEnumerable&lt;int&gt; even = items.Where(x =&gt; x % 2 == 0); beneath it items.Add(10) 之后添加一个项目。它仍将计入您的 DoStuff,因为 even 仍未评估。
  • 如果DoStuffx 迭代even 参数,那么x % 2 == 0 将在内部 执行每个DoStuffx 方法。如果您在调用方法之前使用了ToList,那么x % 2 == 0 将在原始List 中的每个元素中仅出现一次(在任何DoStuffx 调用之前)。

标签: c# linq ienumerable enumerable


【解决方案1】:

为每次调用 DoStuff() 执行,还是以某种方式保存在内存中?

它本身并不保存在内存中,因此以下有时是值得优化的:

IEnumerable<int> even = items.Where(x => x % 2 == 0);   
even = even.ToList();  // now it is held in memory
DoStuff1(even);
DoStuff2(even);
DoStuff3(even);

是否需要 ToList() 取决于 DoStuff() 的作用、源列表与过滤列表的大小、枚举的成本等。

请注意,当 items 稍长一些并且所有 DoStuff 方法都有一个像 foreach(var item in items.Take(3)) 这样的主循环时,主方法中的 ToList() 将是一个昂贵的去优化。

【讨论】:

  • 卡宾枪刚删了
  • @Toshi 那个答案是错误的。 .Net 没有明确地将其存储在内存或缓存中。它将多次执行您的 .Where 语句。并且有一个关于 IEnumerable 的多个枚举的 Resharper/VS 警告
  • @Toshi 抱歉,我的假设是错误的。改正了
【解决方案2】:

澄清:

在你的行中:

IEnumerable<int> even = items.Where(x => x % 2 == 0); 

表达式没有被计算,所以迭代实际上并没有发生。这就是所谓的“延期评估”。注意:这在概念上独立于接口实现,因此无论它是 List 还是 [] 等等(当然可以针对这个主要概念进行实现,但这超出了问题的范围)

执行开始发生在您开始迭代 even 时,例如使用 foreach 或 GetEnumerator() 然后 Next() 或 FirstOrDefault 或 ToList() 或 ToArray() 等。

虽然我们没有看到您的 DoStuffX() 代码,但您可能做了类似的事情。

回答

您要问的主要与上述延迟评估无关:它与缓存有关。如果您不缓存结果,则迭代会发生多次。评估(迭代)和缓存的最简单模式是执行 ToList()

var cached = even.ToList();

(请注意,并非所有 IEnumerable 实现都允许多次迭代,但 List 允许。

【讨论】:

    【解决方案3】:

    他怎么能记住它?不可能。必须执行。

    延迟并不意味着缓存。这意味着该行

    IEnumerable 偶数 = items.Where(x => x % 2 == 0);

    不得执行任何操作。不是开玩笑 - 它可能会被推迟。

    然后在一行中

    DoStuff1(偶数);

    它可以执行。

    当 items 不是内存列表而是数据库时,可能是一个复杂的查询,人们会说“哦,我的 foreach 花了 1 分钟才得到第一行”——这就是 defered 的意思。在这种情况下,它只会发送 SQL 并在请求第一项时执行它。

    【讨论】:

      猜你喜欢
      • 2013-04-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-26
      • 2013-04-02
      • 1970-01-01
      相关资源
      最近更新 更多