【问题标题】:Should I always call .ToArray on LINQ query results returned in a function?我应该总是在函数中返回的 LINQ 查询结果上调用 .ToArray 吗?
【发布时间】:2012-07-18 21:55:19
【问题描述】:

在函数中返回 LINQ 查询的结果时,我遇到了很多 Collection was modified; enumeration operation may not execute 错误的情况,就像这样......(我应该添加函数作为接口的实现和结果让这个模块在另一个模块中使用。)

Public Function GetTheFuzzyFuzzbuzzes() As IEnumerable(of FuzzBuzz) _
    Implements IFoo.GetTheFuzzyFuzzBuzzes

    Return mySecretDataSource.Where(Function(x) x.IsFuzzy)
End Function

如果基础数据有可能被更改,我是否应该在函数或属性 getter 中返回 LINQ 查询结果时始终调用 .ToArray?我知道这样做会降低效率,但我觉得这样做是安全的,因此应该始终这样做以避免时间耦合问题。

编辑:

让我更好地解释问题域。

我们有一个基于图形的主要关注领域的实现,这是一个优化问题。实体表示为图形节点。用各种成本和其他参数加权的边表示节点之间的关系。当用户操作数据时,我们会创建不同的边,并根据当前状态评估他们可以采取的各种选项,从而为他们提供有关每个选项结果的反馈。其他用户和程序对服务器上的数据所做的更改会通过推送技术立即传播到客户端。我们使用了很多线程...

...所有这一切意味着我们有很多事情以非常异步的方式发生。

我们的程序分为多个模块(基于单一职责原则),其中包含一个合同项目和一个在运行时解决的实施项目,这意味着我们严重依赖接口。我们通常使用 IEnumerable 在模块之间传递数据(因为它们是 kind-of-sort-of 不可变的)。

【问题讨论】:

  • 你能发布一个小的、可编译的程序来重现这个吗?
  • 不幸的是,我们的代码库就像 50000+ LOC 和 68 个项目,所以制作一个好的例子会有点困难。请参阅问题中的扩展详细信息。

标签: .net vb.net linq return return-value


【解决方案1】:

如果您要返回一个 IEnumerable(或 IQueryable,或任何类似非自包含的),则限制何时可以调用它、可以用它做什么或可以用多长时间坚持需要清楚地写出来。

出于这些原因,如果这是某种 API(即层之间),我建议返回 FuzzBuzz[] 而不是 IEnumerable<FuzzBuzz>。如果这是类/模块内部实现的一部分,则更容易证明延迟评估的IEnumerable<FuzzBuzz> 是合理的,但使用数组仍然是合理的。

除非结果的数量很大,或者这被频繁调用,否则不太可能成为性能问题(在许多情况下,CPU 时间很便宜,并且分配给数组的内存不会保留很长时间) .

【讨论】:

  • 应该提一下,在某些情况下(比如 NHibernate 的 Linq 实现),将 IQueryable 隐式转换为 IEnumerable 会导致查询运行。
  • 我喜欢提到返回类型应该由 API 决定。我认为我们当中有太多人没有考虑我们对我们的同伴施加的 API。
【解决方案2】:

一般来说,在返回 LINQ 查询的结果时,您不应该总是调用.ToArray.ToList

.ToArray.ToList 都是“贪婪”(与惰性相反)操作,它们实际执行对数据源的查询。调用它们的合适地点和时间是架构决策。例如,您可以在项目中建立一个规则来实现数据访问层内的所有 linq 查询,从而处理那里的所有数据层异常。或者尽可能不执行它们,并且只在最后获得所需的数据。还有许多与此主题相关的其他细节。

但是在从您的函数返回结果时调用或不调用.ToArray — 这不是一个问题,在您提供更详细的示例之前它没有答案。

【讨论】:

  • 如果你想更新你的答案,我已经为问题添加了更多细节(虽然不是详细的代码示例)......
  • 关键是我们的程序被分成模块(基于单一责任原则——你不想混合吗?你好吗?将数据访问我们主要关注领域的基于图形的实现分开?这个答案对于每个项目都是不同的
【解决方案3】:

“作为一项规则”,不,您不应该总是调用 ToList/ToArray。否则,myData.GetSomeSubset().WhereOtherCondition().Join(otherdata) 之类的查询会花费大量时间为每个链接调用分配临时缓冲区。但 LINQ 最适用于不可变集合。您可能希望在修改mySecretDataSource 时更加小心。

具体来说,如果您的代码总是围绕频繁修改数据源而构建,这听起来像是急切返回数组而不是 IEnumerable 的好理由

【讨论】:

    【解决方案4】:

    不,我不会为此制定规则。

    我理解您的担忧。调用方可能不知道它的操作会影响查询结果。

    在某些情况下你真的不能这样做:

    • 在某些示例中,这样做会导致内存不足,例如无限可枚举,或者在每次迭代中生成新计算图像的枚举器中。 (我都有)。
    • 如果您在查询中使用Any()First()。两者都只需要读取第一个元素。所有其他工作都是徒劳的。
    • 如果您希望 Enumerables 与管道/过滤器链接。实现中间结果只是额外的成本。

    另一方面,在许多情况下,当可以想象使用数组会产生影响查询的副作用时,将查询具体化为数组会更安全。

    在编写软件时,“当你需要在 X 和 Y 之间做出选择时,总是做 X”的规则听起来很有吸引力。我不相信有任何这样的规则。也许在 15% 的情况下你真的应该做 X,在 5% 的情况下你肯定需要做 Y,而对于其余的情况,这并不重要。

    对于剩下的 80% 的人来说,什么都不做可能是合适的事情。如果您在任何地方插入ToArray(),代码会错误地暗示这样做是有原因的。

    【讨论】:

    • +1 表示“代码错误地暗示这样做是有原因的”。编写一行代码时,没有人可以安全地删除它。随着时间的推移,它通常会导致意大利面条式代码。
    猜你喜欢
    • 1970-01-01
    • 2019-08-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-30
    • 1970-01-01
    • 2019-08-26
    相关资源
    最近更新 更多