关于c#:IEnumerable< T>

IEnumerable<T> as return type

使用IEnumerable作为返回类型有问题吗?fxcop抱怨退回List(建议退回Collection)。

嗯,我一直被一条规则引导着,"接受你能接受的最少,但返回最大值。"

从这个角度来看,返回IEnumerable是一件坏事,但是当我想使用"懒惰检索"时应该怎么做呢?另外,yield关键字也很不错。


这真是一个由两部分组成的问题。

1)返回IEnumerable是否存在本质上的错误

什么都没有。实际上,如果您使用的是C迭代器,那么这是预期的行为。将其转换为一个列表或另一个集合类不是一个好主意。这样做是对调用方的使用模式进行假设。我觉得对打电话的人做任何假设都不是个好主意。他们可能有充分的理由为什么想要一个IEnumerable。也许他们想将其转换为完全不同的集合层次结构(在这种情况下,到列表的转换是浪费的)。

2)在任何情况下,返回IEnumerable之外的内容是否更可取?

对。虽然这不是一个好主意,假设你的呼叫者,这是完全可以作出决定的基础上,你自己的行为。设想一个场景,您有一个多线程对象,它将请求排队到一个不断更新的对象中。在这种情况下,返回原始的IEnumerable是不负责任的。一旦修改集合,可枚举项将失效,并将导致执行。相反,您可以获取结构的快照并返回该值。以列表形式说。在这种情况下,我只将对象作为直接结构(或接口)返回。

不过,这肯定是更罕见的情况。


不,在这里返回IEnumerable是一件好事,因为您所承诺的就是"一系列(类型化的)值"。是Linq等产品的理想选择,而且非常实用。

调用者可以很容易地将这些数据放入一个列表(或其他任何列表),特别是使用LINQ(ToListToArray等)。

这种方法允许您延迟假脱机返回值,而不必缓冲所有数据。绝对是个好人。前几天我还写了另一个有用的IEnumerable技巧。


关于你的原则:"接受你能接受的最少,但返回最大"。

管理大型程序复杂性的关键是一种称为信息隐藏的技术。如果您的方法是通过构建一个List来工作的,那么通常不需要通过返回该类型来揭示这个事实。如果您这样做了,那么您的呼叫者可以修改他们返回的列表。这会降低您使用yield return进行缓存或延迟迭代的能力。

因此,一个更好的原则是让函数遵循:"尽可能少地揭示你是如何工作的"。


IEnumerable对我来说很好,但它有一些缺点。客户端必须枚举才能获得结果。它无法检查计数等。列表是坏的,因为您暴露了太多的控件;客户机可以从中添加/删除等,这可能是一件坏事情。至少在fxcop看来,收藏似乎是最好的组合。我总是使用在我的上下文中看起来合适的方法(例如,如果我想返回只读集合,我将集合公开为返回类型和返回列表.as read only()或IEnumerable,以便通过yield等进行延迟计算)。以个案为基础


"接受你能接受的最少,但回报最多"是我的主张。当一个方法返回一个对象时,有什么理由我们不能返回实际的类型,并且通过返回一个基类型来限制对象的能力。然而,这提出了一个问题:当我们设计接口时,我们如何知道"最大值"(实际类型)是什么?答案很简单。只有在接口设计器正在设计开放接口的极端情况下(该接口将在应用程序/组件外部实现),他们才不知道实际的返回类型可能是什么。智能设计器应该始终考虑方法应该做什么,以及最佳/一般返回类型应该是什么。

例如,如果我正在设计一个接口来检索对象的向量,并且我知道返回对象的计数将是可变的,我总是假设一个聪明的开发人员总是使用一个列表。如果有人计划返回一个数组,我会质疑他的能力,除非他/她只是从他/她不拥有的另一层返回数据。这可能就是fxcop提倡ICollection(列表和数组的公共基础)的原因。

以上所述,还有两件事需要考虑

  • 返回的数据是可变的还是不可变的

  • 如果返回的数据在多个调用方之间共享

关于LINQ懒惰的评估,我确信95%以上的C用户不理解这些无遗嘱。它是如此的不OO-ish。OO促进方法调用的具体状态更改。LinqLazyEvaluation促进表达式评估模式(而不是非高级用户总是遵循的模式)上的运行时状态更改。


一个重要的方面是,当您返回一个List时,实际上是返回一个引用。这使得调用者可以操纵您的列表。这是一个常见的问题,例如,一个将List返回到GUI层的业务层。


如果只返回一个枚举,那么返回IEnumerable是可以的,并且它将被调用方使用。

但正如其他人指出的,如果调用者需要任何其他信息(例如计数),那么他可能需要枚举。如果返回值未实现ICollection,则.NET 3.5扩展方法ienumerable.count将在后台枚举,这可能是不需要的。

当结果是一个集合时,我经常返回i list或icocollection——在内部,您的方法可以使用一个list并按原样返回,或者如果您想防止修改(例如,如果您在内部缓存列表),则返回list.asreadonly。Afaik Fxcop对这两个都很满意。


仅仅因为你说你要返回IEnumerable并不意味着你不能返回一个列表。其目的是减少不必要的耦合。调用者应该关心的只是获取一个事物列表,而不是用于包含该列表的集合的确切类型。如果你有数组支持的东西,那么得到count之类的东西无论如何都会很快。


我不能接受所选的答案。有一些方法可以处理所描述的场景,但使用列表或其他任何方法都不是其中之一。返回IEnumerable时,必须假定调用方可能会执行foreach。在这种情况下,具体的类型是list还是spaghetti并不重要。事实上,仅仅索引是一个问题,特别是如果删除了项。

任何返回的值都是快照。它可能是IEnumerable的当前内容,在这种情况下,如果缓存了它,它应该是缓存副本的克隆;如果它应该更动态(如SQL查询的结果),则使用yield return;但是,允许容器随意变变,并且提供count和indexer等方法会导致多线程中的灾难。世界。我甚至还没有进入调用者调用你的代码应该控制的容器的添加或删除的能力。

同时返回具体的类型会将您锁定到实现中。今天你可能在内部使用一个列表。明天,您可能会成为多线程的,并希望使用线程安全容器、数组、队列、字典的值集合或LINQ查询的输出。如果您将自己锁定到一个具体的返回类型中,那么在返回之前,您必须更改一组代码或进行转换。


我认为你自己的指导是很好的——如果你能够更具体地了解你返回的内容而不影响性能(你不必根据你的结果建立一个列表),那么就这么做。但是,如果您的函数合法地不知道它将找到什么类型,比如在某些情况下,您将使用列表,在某些情况下使用数组等,那么返回IEnumerable是您所能做的"最佳"操作。把它看作是你想要返回的所有事物的"最大公倍数"。