使用IEnumerable<T>
作为返回类型是否有问题?
FxCop抱怨返回List<T>
(建议返回Collection<T>
)。
好吧,我一直受到一条规则的指导“尽你所能接受,但要归还最大值。”
从这个角度来看,返回IEnumerable<T>
是一件坏事,但是当我想使用“懒惰检索”时我该怎么办?此外,yield
关键字也是如此。
答案 0 :(得分:29)
这实际上是一个两部分问题。
1)返回IEnumerable&lt; T&gt;
是否存在任何错误?一点也不。实际上,如果您使用的是C#迭代器,则这是预期的行为。将其转换为列表&lt; T&gt;或者其他集合类先发制人并不是一个好主意。这样做是由调用者对使用模式进行假设。我发现假设调用者的任何事情并不是一个好主意。他们可能有充分的理由为什么他们想要IEnumerable&lt; T&gt;。也许他们想将它转换为完全不同的集合层次结构(在这种情况下,浪费了转换为List)。
2)在任何情况下,最好返回IEnumerable&lt; T&gt;以外的其他内容吗?
是。虽然假设你的呼叫者并不是一个好主意,但根据你自己的行为做出决定是完全可以的。想象一下这样一个场景,你有一个多线程对象,它将请求排队到一个不断更新的对象中。在这种情况下,返回原始IEnumerable&lt; T&gt;是不负责任的。一旦修改了集合,枚举就会失效并导致执行。相反,您可以拍摄结构的快照并返回该值。在列表中说&lt; T&gt;形成。在这种情况下,我只是将对象作为直接结构(或接口)返回。
这当然是最罕见的情况。
答案 1 :(得分:27)
不,IEnumerable<T>
是好的东西要返回这里,因为你所承诺的是“一系列(类型)值”。理想的LINQ等,完全可用。
调用者可以轻松地将此数据放入列表(或其他任何内容) - 尤其是使用LINQ(ToList
,ToArray
等)。
这种方法允许您懒惰地回退值,而不是必须缓冲所有数据。绝对是一个好东西。我前几天写了another useful IEnumerable<T>
trick。
答案 2 :(得分:4)
IEnumerable很好,但它有一些缺点。客户端必须枚举才能获得结果。它无法检查Count等。 列表很糟糕,因为你暴露了太多的控制;客户端可以添加/删除等等,这可能是一件坏事。 收藏似乎是最好的妥协,至少在FxCop看来。 我总是在我的上下文中使用似乎合适的东西(例如,如果我想返回一个只读集合,我将集合公开为返回类型并返回List.AsReadOnly()或IEnumerable以通过yield等进行惰性求值)。根据具体情况来看待
答案 3 :(得分:4)
关于你的原则:“尽可能接受,但要返回最大值”。
管理大型程序复杂性的关键是一种称为信息隐藏的技术。如果您的方法通过构建List<T>
来工作,则通常不必通过返回该类型来揭示此事实。如果您这样做,那么您的呼叫者可以修改他们返回的列表。这使您无法使用yield return
进行缓存或延迟迭代。
因此,一个更好的原则就是遵循以下功能:“尽可能少地揭示你的工作方式”。
答案 4 :(得分:3)
“尽可能接受,但返回最大值”是我所倡导的。当一个方法返回一个对象时,我们必须通过返回一个基类型来返回实际类型并限制该对象的功能。然而,这提出了一个问题,我们如何知道设计界面时“最大”(实际类型)是什么。答案很简单。只有在接口设计人员设计开放接口的极端情况下,它才会在应用程序/组件之外实现,他们不知道实际的返回类型是什么。聪明的设计师应该始终考虑方法应该做什么以及最佳/通用返回类型应该是什么。
E.g。如果我正在设计一个接口来检索对象的向量,并且我知道返回的对象的数量将是可变的,我将始终假设智能开发人员将始终使用List。如果有人计划返回一个数组,我会质疑他的能力,除非他/她只是从他/她不拥有的另一层返回数据。这可能就是为什么FxCop提倡ICollection(List和Array的共同基础)。
如上所述,还有其他几件事需要考虑
如果返回的数据应该是可变的或不可变的
如果返回的数据在多个呼叫者之间共享
关于LINQ懒惰评估,我确信95%+ C#用户不理解这些内容。它是如此非oo-ish。 OO促进方法调用的具体状态更改。 LINQ延迟评估促进表达式评估模式的运行时状态更改(不是非高级用户总是遵循的)。
答案 5 :(得分:2)
返回IEnumerable&lt; T&gt;如果你真的只返回一个枚举,那就没问题了,你的调用者就会这样做。
但正如其他人所指出的,它的缺点是调用者可能需要枚举他是否需要任何其他信息(例如Count)。如果返回值没有实现ICollection&lt; T&gt;,那么.NET 3.5扩展方法IEnumerable&lt; T&gt; .Count将在幕后进行枚举,这可能是不合需要的。
我经常返回IList&lt; T&gt;或ICollection&lt; T&gt;当结果是一个集合时 - 你的方法在内部可以使用List&lt; T&gt;如果您想要防止修改(例如,如果您在内部缓存列表),则按原样返回,或返回List&lt; T&gt; .AsReadOnly。 AFAIK FxCop对其中任何一个都非常满意。
答案 6 :(得分:2)
一个重要方面是,当您返回List<T>
时,您实际上会返回引用。这使得调用者可以操作您的列表。这是一个常见问题 - 例如,将List<T>
返回到GUI层的业务层。
答案 7 :(得分:1)
仅仅因为你说你正在返回IEnumerable并不意味着你不能返回一个List。这个想法是减少不必要的耦合。调用者应该关心的只是获取事物列表,而不是用于包含该列表的确切集合类型。如果你有一些由数组支持的东西,那么无论如何得到像Count这样的东西都会很快。
答案 8 :(得分:0)
我认为你自己的指导很棒 - 如果你能够更加具体地了解你在没有性能损失的情况下返回的内容(你不必例如在你的结果中建立一个List),那么就这样做。但是如果你的函数合法地不知道它将找到什么类型,比如在某些情况下你将使用List和一些使用数组等,那么返回IEnumerable是你可以做的“最好的” 。把它想象成你可能想要回归的所有东西的“最常见的倍数”。
答案 9 :(得分:0)
我无法接受所选择的答案。有一些方法可以处理所描述的场景,但使用List或其他任何你使用的场景不是其中之一。返回IEnumerable的那一刻,你必须假设调用者可能会做一个foreach。在这种情况下,具体类型是List还是意大利面并不重要。事实上,只有索引是一个问题,特别是如果删除项目。
任何返回的值都是快照。它可能是IEnumerable的当前内容,在这种情况下,如果它被缓存,它应该是缓存副本的克隆;如果它应该更加动态(比如sql查询的结果)那么使用yield return;然而,允许容器随意变异,并提供像Count和indexer这样的方法,这是多线程世界中的灾难。我甚至没有能够让调用者在你的代码应该控制的容器上调用Add或Delete。
同样返回具体类型会将您锁定到实现中。今天在内部您可能正在使用列表。明天,你可能会变成多线程,并且想要使用线程安全容器或数组或队列,或者使用字典的Values集合或Linq查询的输出。如果您将自己锁定为具体的返回类型,则必须在返回之前更改一堆代码或进行转换。