何时返回延迟的 IEnumerable<T> 是一个不好的做法?

12

我想知道是否有任何关于何时返回延迟的 IEnumerable<T> 或在从函数返回之前调用 ToArray() 的经验法则或最佳实践。

例如,作为 API 的使用者,我认为像 IEnumerable<Widget> GetWidgets() 这样的方法在我调用它时抛出 HttpException 而不是在枚举结果时抛出会更好。

public IEnumerable<Widget> GetWidgets(IEnumarable<int> widgetIds) {
    return widgetIds.Select(id => GetWidgetFromWidgetWebService(id));
}

但是,异常不应该无论如何都被抛出吗?至少在这种情况下它们会以相同的方式工作。 - rae1
1
@rae1n 是的,无论如何都会抛出异常,但是想象一下一个延迟的 IEnumerable<Widget> 被传递了一两层,只有在远离原始集合返回的地方调用时才抛出异常。 - joshperry
在我的例子中,当返回的 IEnumerable<Widget> 被迭代时,它将调用 GetWidgetFromWidgetWebService(),因此每次枚举器的迭代都有可能抛出异常。 - joshperry
@joshperry:很抱歉,我无法理解这个问题。一个 Web API 是否可以返回一个延迟的 IEnumerable<T>? - shahkalpesh
@shahkalpesh 我认为乔希考虑了多次调用Web服务,每次调用返回一个小部件... - Reed Copsey
显示剩余9条评论
2个回答

6

如果延迟执行没有太多的副作用,我总是更喜欢返回一个延迟执行的IEnumerable<T>。例如,如果可枚举对象基于一个可能会发生改变的内部集合,我更倾向于先评估它。

但是,如果可枚举对象正在进行计算等操作,那么我通常会将其延迟执行。


调用者可以随时调用 ToList() 并强制立即评估(集合)。我认为最好让调用者做出这个选择,除非,正如你所说,有充分的理由不这样做。 - Jon B
@JonB 这是我的想法 - 我更喜欢在可能的情况下允许优化。 - Reed Copsey
2
只有一种例外情况,那就是当你将IEnumerable传递到一个无法枚举它的上下文中时。例如,在使用EF上下文生成IEnumerable<T>时,此时上下文已经被释放。除此之外,我同意保持延迟执行以便更好地进行组合。 - John Saunders
4
唯一的问题在于,返回 IEnumerable<T> 只告诉调用者,“嘿,这里有一个接口可以遍历类型为 T 的一组事物”,它并没有传达它可能是延迟加载还是即时加载的信息。我不知道调用者如何获得足够的信息来做出明智的决定是否立即将列表实现化。 - joshperry
@joshperry 文档在这里非常有帮助。 话虽如此,我并不觉得这是总是完全评估它的足够理由,因为通常有重要的优点,尤其是对于较大的枚举,推迟事情会更好。例如,我讨厌看到一个不必要地被评估的API通过.Any()被使用(这并不罕见)... - Reed Copsey
@JohnSaunders,这基本上就是我所说的 - 如果会有明显的副作用造成问题,我会进行评估。一个已释放的EF上下文就是一个很好的例子。 - Reed Copsey

1

如果您的可枚举对象可能会抛出异常,尽早地评估它(如果可能的话)。您不希望错误发生在与错误原因无关的远程位置。您希望错误发生在它引起的地方。

毕竟,该方法没有完成其名称所宣传的内容,因此应该抛出异常。

在这种情况下,我通常将返回类型更改为IList<T>,以记录它的急切执行。


我非常喜欢尝试用我的代码表达意图。但是,当我在这些情况下返回IList<T>时,唯一的问题是它暗示了“嘿,这是一个可变集合”。我不确定有什么更好的选择;也许是T[],也许是IReadOnlyList<T> - joshperry
@joshperry 我很少修改集合,所以我没有那个问题。通常我会创建一个新的集合,自从C# 3.0之后这非常容易。 - usr
没有什么可以证明 IList<T> 是急切执行的。拥有一个只在必要时才执行必要操作的 IList<T> 是完全可能的,而且通常是可取的。 - Jon Hanna
@JonHanna 确实有可能,但根据我的经验,这种做法并不常见。我无法列举出任何一个 BCL 类来这么做,也从未亲自尝试过。我认为 IList 是记录渴望度的好方式,因为没有其他更好的方法来记录它。 - usr
我这样做是因为我想要缓存结果以便后续重用,而不浪费在第一次检索时的时间,或者当我可能只想对前几个元素进行某些操作,但有时只加载整个内容。如果我想要明确地急切加载,我会返回List<T>或另一个具体类。毕竟,返回任何接口的原因之一是说“我是如何做到的,不是你关心的问题,并且将来可能会改变”;这恰恰与清楚地表明你是如何做某事相反。 - Jon Hanna

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接