【问题标题】:Foreach using LINQ Efficiency [closed]使用 LINQ 效率的 Foreach [关闭]
【发布时间】:2021-08-19 21:30:28
【问题描述】:

我一直在想,它一直困扰着我,用什么方式编写使用 LINQ 的 foreach 语句更有效。

据我所知,ToList() 在内存中创建对象,而 IEnumerable 进行引用,并且仅在需要数据时过滤数据以供使用。

问题是,foreach 语句是在每次迭代时调用 List/IEnumerable,还是只调用一次并将该对象/List 保存在内存中?

查看以下内容,哪个选项最有效以及出于什么原因?

  1. 选项 A

    foreach (Car car in CarList.Where(x => x.Make == "BMW")) {}
    
  2. 选项 B

    foreach (Car car in CarList.Where(x => x.Make == "BMW").ToList()) {}
    
  3. 选项 C

    IEnumerable<Car> myCarList = CarList.Where(x => x.Make == "BMW");
    foreach (Car car in myCarList) {}
    
  4. 选项 D

    IEnumerable<Car> myCarList = CarList.Where(x => x.Make == "BMW").ToList();
    foreach (Car car in myCarList) {}
    

【问题讨论】:

  • 我的猜测是 A 和 C 是最快的,但这真的取决于 CarList 是什么。与任何与性能相关的事情一样,唯一的判断方法就是自己测试。
  • 你再也不用问这种类型的问题github.com/dotnet/BenchmarkDotNet
  • @faso 不,永远不要使用秒表进行基准测试,它非常不可靠。使用合适的工具,例如 BenchmarkDotNet
  • @DavidG 说了什么
  • a 和 c 基本相同,b 和 d 也一样。一般来说 a 和 c 会更快,因为它们不会产生创建和调整列表大小的成本(快多少很大程度上取决于列表的大小)。选项 e(完全不使用 LINQ 的标准 foreach 中的检查)几乎总是比所有选项都快。

标签: c#


【解决方案1】:

看看这个(诚然旧的)答案:Does "foreach" cause repeated Linq execution?

这在一定程度上取决于数据集;但由于 LINQ 和 IEnumerables 的工作方式,A & C 在功能上是相同的。而不是一次性执行查询;结果以流式方式检索,即逐一检索。每次迭代器调用MoveNext 时,投影都会应用于下一个对象;因为您的示例中有一个 where 子句,它在投影之前应用了过滤器。

通过在示例 B 和 D 中调用 .ToList() 方法,您将强制执行查询并将结果缓存。就“哪个更好”的问题而言,这就是答案变成“视情况而定”的地方。

如果数据集已经是内存中的对象; A & C 都节省了一点内存,并且比 B & D 稍微快一些,因为它不需要在调整列表大小方面进行任何操作。

如果您正在查询数据库,那么 A & C 会节省内存;但是(您必须测试这一点,因为它似乎很受欢迎)每次MoveNext 被点击时它可能会返回数据库 - 在一张小桌子上它不会有太大区别,但是我在大型表中遇到过实例,仅通过创建查询结果的本地列表就可以节省几分钟的执行时间。

为清楚起见进行编辑:

添加一些伪代码来详细说明这一点。 A & C 工作原理的前提如下:

  1. 寻找符合条件的元素。
  2. 获取第一个满足选择条件的元素。
  3. 执行循环中的任何操作。
  4. 寻找另一个元素。
  5. 获取下一个元素。
  6. 执行循环中的任何操作。
  7. 重复步骤 4-6,直到找不到结果。

而 B & D 则更多地遵循以下原则:

  1. 查找所有符合选择标准的元素。
  2. 从结果到步骤 1 创建一个列表。
  3. 分配一个指向列表中第一个元素的指针。
  4. 在循环中执行代码。
  5. 将指针移至列表中的下一项。
  6. 在循环中执行代码。
  7. 对列表中的所有项目重复第 5 步和第 6 步。

一个更真实的场景可以大致解释它是当你去购物时——如果你手上有购物清单,因为你已经花时间弄清楚你需要什么,(B&D)那么您只需要查看列表并获取下一项即可。如果您没有购物清单 (A & C),那么您在商店中还有额外的步骤来思考“我需要什么?”在检索项目之前。

【讨论】:

  • it's possible that it'd go back the DB each time the MoveNext is hit 但是 B&D 不会受到同样成本的影响吗?为什么 A & C 会产生这笔费用,而 D & D 不会? 两个都在打电话给MoveNext
  • 为了清楚起见,我稍后会对其进行编辑;但本质上,ToList 使它进入数据库并在执行循环之前获取所有结果并将它们存储在本地。我将编辑一些伪代码以进一步解释它。
  • 我怀疑你会发现 foreaching 在大多数情况下它的行为类似,因为 DB 层将执行批处理请求(不是一次一行)。 docs.microsoft.com/en-us/ef/core/performance/…
  • @mjwills 我没见过那个;这就是为什么我看到它在某些环境中运行良好但在其他环境中运行良好的原因。我事先没有想到,但现在你已经向我展示了,我记得我在执行 for 循环时看到查询重复命中数据库的地方是不使用实体的地方连接框架。
  • 您假设这是一个 EF(或类似)查询。我们只是不知道。
猜你喜欢
  • 2017-08-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-28
相关资源
最近更新 更多