繁体   English   中英

可枚举<t>作为返回类型</t>

[英]IEnumerable<T> as return type

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

好吧,我一直遵循一条规则“接受最少的东西,但返回最多的东西”。

从这个角度来看,返回IEnumerable<T>是一件坏事,但是当我想使用“惰性检索”时该怎么办呢? 另外, yield关键字是个好东西。

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

1)返回IEnumerable <T>本身有什么问题

一点也不。 实际上,如果您使用的是C#迭代器,则这是预期的行为。 先发制人地将它转换为List <T>或其他集合类并不是一个好主意。 这样做是由调用者对使用模式进行假设。 我发现假设调用者的任何事情并不是一个好主意。 他们可能有充分的理由为什么他们想要一个IEnumerable <T>。 也许他们想将它转换为完全不同的集合层次结构(在这种情况下,浪费了转换为List)。

2)在任何情况下,最好还是返回IEnumerable <T>以外的东西吗?

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

这当然是罕见的情况。

不, IEnumerable<T>是回到这里一件好事 ,因为你是有希望的是“(类型)值序列”。 LINQ等的理想选择,完全可用。

调用者可以轻松地将此数据放入列表(或其他任何内容) - 尤其是使用LINQ( ToListToArray等)。

这种方法允许您懒惰地回退值,而不是必须缓冲所有数据。 绝对是一个好东西。 我前几天写了另一个有用的IEnumerable<T>技巧

IEnumerable很好,但它有一些缺点。 客户端必须枚举才能获得结果。 它没有办法检查Count等。列表是坏的,因为你暴露了太多的控制; 客户端可以添加/删除等等,这可能是一件坏事。 收藏似乎是最好的妥协,至少在FxCop看来。 我总是在我的上下文中使用似乎合适的东西(例如,如果我想返回一个只读集合,我将集合公开为返回类型并返回List.AsReadOnly()或IEnumerable以通过yield等进行惰性求值)。 根据具体情况来看

关于你的原则:“尽可能接受,但要回报最大”。

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

因此,一个更好的原则是要遵循的功能是:“尽可能少地揭示你的工作方式”。

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

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

如上所述,还有其他几件事需要考虑

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

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

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

如果你真的只返回一个枚举,那么返回IEnumerable <T>就可以了,并且你的调用者将会这样做。

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

当结果是集合时,我经常返回IList <T>或ICollection <T> - 在内部,您的方法可以使用List <T>并按原样返回,或者如果要保护则返回List <T> .AsReadOnly反对修改(例如,如果您在内部缓存列表)。 AFAIK FxCop对其中任何一个都非常满意。

一个重要的方面是,当您返回List<T>您实际上返回了一个引用 这使得调用者可以操作您的列表。 这是一个常见问题 - 例如,将List<T>返回到GUI层的Business层。

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

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

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

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

我认为你自己的指导是很棒的 - 如果你能够更加具体地了解你在没有性能损失的情况下返回的内容(你不必例如在你的结果中建立一个List),那么这样做。 但是如果你的函数合法地不知道它将找到什么类型,比如在某些情况下你将使用List和一些使用数组等,那么返回IEnumerable是你可以做的“最好的” 。 将其视为您可能想要返回的所有内容的“最常见的多重”。

IEnumerable很酷,因为您可以使用 yield 迭代器为消费者提供他们需要的数据,但构造中隐藏了成本。

让我用一个例子来解释它。 假设我正在使用这种方法:

IEnumerable GetFilesFromFolder(字符串路径)

那么,我得到了什么? 要获取我的文件夹中的所有文件,我必须迭代枚举,这很好,毕竟枚举就是这样工作的,但是如果出于某种原因我必须枚举它两次怎么办?

第二次我应该期待刷新的结果还是幂等的? 我不知道。 我必须检查库/方法的文档。

调用消费者完成的枚举的GetEnumerator方法,实际上可以在幕后执行 I/O 操作,或 http 调用,或者它可以简单地迭代一个内部数组,我不能确定. 我必须检查文档,希望记录这种行为。

这个细节重要吗? 我认为是的。 至少从性能的角度来看。

即使迭代成本很慢且受 CPU 限制,这也不是零,并且在枚举链的情况下它可能 go 更糟,这通常会使调试会话成为一场噩梦。

我不想让我的库的使用者产生疑虑,所以每当我知道我的 API 返回的元素很少时,我总是使用 arrays 作为返回类型,只有当要返回的数据很大时,我才使用 IEnumerable 或 IAsyncEnumerable。

无论如何,如果你想返回枚举,请记录你的 API 以告诉消费者结果是否是快照。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM