2008-12-19 101 views
28

使用IEnumerable<T>作为返回类型有问题吗? FxCop抱怨返回List<T>(建议改为返回Collection<T>)。IEnumerable <T>作为返回类型

那么,我一直遵循的规则是“至少接受,但最大回报。”

从这个角度来看,返回IEnumerable<T>是一件坏事,但我想在使用“懒惰检索”时该怎么办?此外,yield关键字是这样一个好人。

回答

27

这实际上是一个两部分问题。

1)是否有内在什么毛病都返回一个IEnumerable <牛逼>

什么都没有。事实上,如果你使用C#迭代器,这是预期的行为。将它转换为列表<T>或另一个集合类先发制人并不是一个好主意。这样做是由你的调用者对使用模式作出假设。我觉得假设任何关于调用者的信息并不是一个好主意。他们可能有很好的理由为什么他们想要IEnumerable <T>。也许他们希望将其转换为完全不同的集合层次结构(在这种情况下,浪费到List的转换)。

2)是否有任何情况下可能会优先返回IEnumerable以外的东西<T>?

是的。虽然假设你的来电者并不是一个好主意,但根据自己的行为做出决定是完全可以的。想象一下你有一个多线程对象,它将请求排入一个不断更新的对象中。在这种情况下返回一个原始IEnumerable <T>是不负责任的。只要集合被修改,枚举就会失效并且会导致执行错误。相反,您可以拍摄结构快照并返回该值。在列表中说<T>表格。在这种情况下,我只是将对象作为直接结构(或接口)返回。

虽然这确实是罕见的情况。

27

不,IEnumerable<T>事情要回到这里,因为你所有的承诺是“一个(键入)值序列”。理想的LINQ等,并且完全可用。

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

这种方法允许您懒惰地后退值,而不必缓冲所有的数据。肯定是一个好人。前些日子也写了another useful IEnumerable<T> trick

+1

关于为什么fxcop抱怨的任何想法? – annakata 2008-12-19 15:19:25

+0

它是否抱怨返回IEnumerable ?如果是这样,用少许盐......它并不总是正确的。 – 2008-12-19 15:21:46

+3

如果它提示你考虑你真的想要返回什么,fxcop已经完成了它的工作; -p – 2008-12-19 15:22:20

4

IEnumerable对我来说很好,但它有一些缺点。客户端必须枚举才能得到结果。它没有办法检查计数等。 列表是不好的,因为你暴露在很多控制;客户端可以添加/删除等等,这可能是一件坏事。 收藏似乎是最好的折衷方案,至少在FxCop看来。 我总是使用什么似乎appropiate在我的上下文中(例如,如果我想返回一个只读集合我公开集合作为返回类型并返回List.AsReadOnly()或IEnumerable通过产量等惰性评估)。以个案为基础采取它

1

仅仅因为你说你要返回IEnumerable并不意味着你不能返回一个List。这个想法是减少不必要的耦合。调用者应该关心的所有内容都是获取事物的列表,而不是用于包含该列表的收集的确切类型。如果你有一个由数组支持的东西,那么得到像Count这样的东西反正会很快。

0

我认为你自己的指导是非常好的 - 如果你能够更加具体地了解你在没有性能影响的情况下返回什么(你不必例如在结果中创建一个列表),那么做。但是,如果你的函数合法地不知道它会找到什么类型,就像在某些情况下你会使用一个List,有些使用Array等等,然后返回IEnumerable是你可以做的“最好的” 。把它想象成你可能想要返回的一切的“最大的共同倍数”。

2

返回IEnumerable <T>是确定的,如果你真的只返回一个枚举,并且它将被你的调用者这样使用。

但正如其他人指出的那样,它有一个缺点,即如果需要其他信息(例如Count),调用者可能需要枚举。 .NET 3.5扩展方法IEnumerable <T>。如果返回值不实现ICollection <T>,则计数将在幕后枚举,这可能不合需要。

我经常回IList的<牛逼>或ICollection的<牛逼>当结果是一个集合 - 内部你的方法可以使用列表<牛逼>,要么返回它就好了,或者返回列表<牛逼> .AsReadOnly如果你想防止修改(例如,如果你在内部缓存列表)。 AFAIK FxCop对这些都很满意。

3

关于您的原则:“尽可能地接受,但返回最大值”。

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

所以一个更好的原则是函数遵循的是:“尽可能少地揭示你的工作方式”。

2

一个重要的方面是,当您返回List<T>时,您实际返回参考。这使得调用者可以操纵你的列表。这是一个常见的问题 - 例如,一个将List<T>返回给GUI层的业务层。

3

“接受最少可以,但返回最大值”是我所倡导的。当一个方法返回一个对象时,我们不得不返回实际的类型,并通过返回一个基类型来限制对象的能力。然而,这提出了一个问题,我们如何知道在设计接口时“最大”(实际类型)会是什么。答案很简单。只有在界面设计者正在设计一个开放接口的极端情况下,它将在应用程序/组件之外实现,他们不知道实际返回类型可能是什么。聪明的设计师应该总是考虑该方法应该做什么以及最佳/通用返回类型应该是什么。

E.g.如果我正在设计一个接口来检索一个对象向量,并且我知道返回的对象的数量将会变化,我总是会假设一个聪明的开发人员总是会使用一个List。如果有人计划返回一个数组,我会质疑他的能力,除非他/她只是从他/她不拥有的另一层返回数据。这可能就是为什么FxCop鼓吹ICollection(List和Array的共同基础)的原因。

以上虽这么说,还有几个其他的事情要考虑

  • 如果返回的数据应该是可变的或不可变的

  • 如果返回的数据在多个呼叫者共享

关于LINQ惰性评估我相信95%+ C#用户不理解这些障碍。这是非常不错的。 OO促进方法调用的具体状态更改。 LINQ懒惰评估促进表达式评估模式的运行时状态更改(不是非高级用户始终遵循的东西)。

0

我不能接受选择的答案。有些方法可以处理所描述的场景,但是使用List或其他您使用的不是其中之一。 IEnumerable返回的那一刻,你必须假定调用者可能会做一个foreach。在这种情况下,具体类型是List还是意大利面条并不重要。事实上,索引是一个问题,特别是如果项目被删除。

任何返回值都是快照。它可能是IEnumerable的当前内容,在这种情况下,如果它被缓存了,它应该是缓存副本的克隆;如果它应该更加动态(就像sql查询的结果一样),然后使用yield return;然而允许容器随意变异并且提供像Count和indexer这样的方法是多线程世界中的灾难处理方法。我甚至没有意识到调用者调用代码应该控制的容器上的Add或Delete的能力。

还返回一个具体类型锁定你的实现。今天在内部,您可能正在使用一个列表。明天也许你确实变成了多线程的,并且想要使用线程安全的容器或者数组或者队列或者字典的Values集合或者Linq查询的输出。如果您将自己锁定在具体的返回类型中,则必须更改一堆代码或在返回之前执行转换。

相关问题