when to return IEnumerable<T>
只有在方法和属性被延迟评估的情况下,我才应该从该方法和属性返回IEnumerable?
当你返回IEnumerable、ICollection和IList时,你们有什么模式吗?
- 您可以在以下链接找到答案:stackoverflow.com/questions/1072614/&hellip;stackoverflow.com/questions/3559868/&hellip;
- @谢尔盖,拉维亚-行动党的标准,懒惰的评估,使它微妙地不同于其他环节。
每当调用方只需要从头到尾迭代集合时,我返回IEnumerable。通过使用尽可能最小的接口,我可以确保调用代码没有太紧密地耦合,从而更容易在以后更改方法或属性中的代码。如果它仍然返回IEnumerable,没有其他人需要关心。如果它之前返回了list,并且新方法在内部表示使用哈希集的东西,那么其他的东西也会突然发生变化。
因此,我总是倾向于返回集合接口,而不是具体的集合,除非在调用代码确实需要知道它处理的集合类型的情况下。我认为调用者需要能够执行的操作,并找到支持它们的最小接口。
- 但是返回IEnumerable是一种特殊情况——它会改变返回到迭代器方法的行为——我认为这就是OP所得到的。
- @Matthew,如果您正在编写其他开发人员将要使用的代码,那么不清楚他们将如何使用它,他们只需要迭代还是需要知道计数等。
- Rob,是的,这有时会导致一些问题,但我倾向于让调用者在需要的时候通过急切地列举返回来处理它(但是,我也是一个haskell程序员)。丹尼尔,我的措词可能不好,因为这个方法的作者也会影响调用者可以用它做什么。
通常,如果它是一个列表(不是懒惰的),我会返回i list。比如,当我的方法被期望作为集合返回结果时,我总是这样做。我这样做是因为它在方法签名中清楚地声明它是一个已加载的列表,而不是一个延迟计算的列表,但是使用接口不会公开具体的类型(数组、列表、只读列表等)。
当对传入的集合执行任何类型的转换时,返回IEnumerable(Linq方法)
同时,我在方法中使用IEnumerable作为参数,只是声明"这个函数不重要,它只需要能够通过"。当然,我确保IEnumerable在我的函数中只枚举一次。
在这些情况下,应退回IEnumerable:
结果不管是数组、列表还是任何类型的集合,因为使用者只需要迭代它。
结果是使用IEnumerable的实现进行类型化的,但是公共API并不强制消费者使用特定于API的类型,因此消费者使用poco保持中立。
如果预期结果为集合,则应返回IEnumerable的具体实现,如ICollection、IList、List等。这就是消费者应该能够使用索引器访问可枚举成员,或者他们应该修改集合本身(添加、更新、删除、搜索…集合中的项)。
为属性和返回类型选择正确的具体类型更多的是一个好的案例分析,只需输入您的消费者需要的内容。
例如,从概念上讲,一些属性应该返回一个列表,但消费者不应该修改它,因此,这个属性应该返回一个ReadOnlyCollection,但您应该将其键入IList。
正如我上面所说,很好的案例分析并保持简单。
如果对方法的调用方来说结果应该是只读的,则返回IEnumerable。
- 这个答案并没有解决迭代器方法与正规方法的本质问题以及优缺点。
- 如果我返回新的列表,那么我不在乎它是否是只读的。我的意思是我返回的新列表不是任何对象的成员。
- +我以前从没想过这个。所以用另一个枚举器包装任何类型的集合,以确保集合是真正的只读的(考虑到它们的元素是不可变的),而不仅仅是返回它,不是吗?
- @穆图:不,我大部分时间都是返回一个List。所以如果有人想把它列成一个单子,他可以。这都是关于通信的:返回IEnumerable后,我通信结果只应迭代,而不应更改。这对于属性尤其适用。
- @罗布列文:我不明白。想把你的评论扩大一点吗?当你提到iterator method时,你的意思是什么?
- @丹尼尔-你的观点很好,但还有另外一个考虑。通过使返回类型成为IEnumerable可以将该方法转换为迭代器方法。它的行为现在完全不同于它被召唤的时间。它不会在代码中"看起来像"时调用,它只在对枚举器进行评估时调用——这会导致懒惰的行为。这有优点也有缺点——我想也许你可以稍微扩展一下,使答案更加完整。
- @daniel-msdn.microsoft.com/en-us/library/dscy5s0(v=vs.80).aspx
- @罗布列文:那是不正确的,对不起。也许你应该读一下那个话题。你在这里混东西。返回IEnumerable只是松散地与yield关键字相关。
- var result = Method1(); // <- Executed here, as long as it internally uses return (and not yield return), even if the return type is IEnumerable
- 事实上-我应该是具体的-它只在yield-return关键字出现时改变行为。但这肯定是在作战问题中隐含的——他问的是正常与懒惰的评估。这就是他问题的核心,不是吗?我读到这个问题是为了说"和我谈谈普通方法和懒惰的评价IEnumerator的对比"。
- @Roblevine:我在读"您的方法签名设计决策基于什么实现元素?"
- @丹尼尔:好吧,那么使用IEnumerable对消费者来说是一种暗示,而不是一种有力的保证?(尽管我承认这在大多数环境中是非常抽象的,但消费者可能会将返回值强制转换回IList,如果将其转换为真正的迭代器方法,这是不可能的。)
- @穆图:没错。
- 另一个快速点-在没有yield关键字的情况下(即,当这不是迭代器方法时),它只部分地将返回值保护为只读。它仍然可以强制转换为底层类型,然后进行变异。我之所以对您最初谈论的迭代器方法的假设进行了短路,是因为您所提议的保护只有迭代器方法才真正存在。(编辑:刚刚发布了这篇文章,看看穆图也提出了同样的观点!)
- @罗布列文:正如我之前对穆图的回复,这是关于意图的交流。在完全信任的环境中,您无论如何都不能"保护"或"强制"。
- 就意图而言——你不能从一个迭代器方法(使用yield的方法)和一个不使用方法签名的方法中分辨出来,即使它的行为完全不同。是否也可能存在"意图"问题?您如何表达"这是一个迭代器方法,并且行为将不同于它的签名"的意图?是否存在不使用IEnumerable的情况,因为它可能错误地表明这是一个迭代器方法的意图?或者,如果是,所有使用者都应该编写代码(例如,某些迭代器方法可能不是为枚举器的多个"传递"而设计的)?
根据经验:
我返回ICollection,如果我确定我总是知道返回的结果数量。如果呼叫者只是列举,他们可以很容易地将其视为一个IEnumerable。如果元素的顺序不是随机的,我使用IList。我在其他情况下使用IEnumerable。对于一个接口的不同实现,我使用"最宽松"的接口,而不是重新复制结果以满足特定的集合接口。