foo
,它创建并返回List<T>
类型。ReSharper建议我将返回类型更改为IEnumerable<T>
。然而,我知道在调用foo
的函数中,有些函数会想要访问List中的随机元素,因此需要首先调用IEnumerable<T>.ToList()
。我应该将返回类型更改为
IEnumerable<T>
吗?为什么?foo
,它创建并返回List<T>
类型。ReSharper建议我将返回类型更改为IEnumerable<T>
。然而,我知道在调用foo
的函数中,有些函数会想要访问List中的随机元素,因此需要首先调用IEnumerable<T>.ToList()
。IEnumerable<T>
吗?为什么?foo
的调用者想要访问列表中的随机元素,它将不建议您返回IEnumerable<T>
。只有当您完全了解情况后,您应该遵循或不遵循Resharper的建议,这些建议通常非常好。如果一个方法返回 List<T>
,通常意味着它已经构造并填充了一个新的 List<T>
实例,以便于调用者使用。调用者可以自由地修改该集合,并且除非被调用者给予引用,否则没有其他东西会修改该集合。返回数组的方法有类似的期望。如果一个方法返回一个集合接口而不是列表或数组类型,则通常表明它可能返回对可能是惰性生成的对象或与其他代码共享数据的引用,并且想要一个可变集合实例的调用者应该将其收到的集合传递给像 ToList
这样的方法,并使用后者方法返回的集合。
List<T>
实例,那么它应该使用表明这一点的返回类型(即 List<T>
或 T[]
)。如果它可能受益于能够返回现有集合或其包装器,则返回其他类型可能更好。请注意,如果调用者需要在给定除 List<T>
以外的其他东西时调用 ToList
,即使返回 List<T>
的方法必须复制列表,而返回其他类型的方法不必这样做,使方法本身返回 List<T>
仍然是一种“胜利”。如果某个方法无论返回类型如何都必须构造一个新的集合实例,则返回任何除 List<T>
以外的内容都将是一种损失,如果任何调用者最终需要调用 ToList
。
有时候比较有帮助的替代方法是,让一个类提供一个方法来接收一个像Action<T>
这样的参数,并在集合中的每个项目上调用它。想要构建包含多个集合中所有项目的列表的代码可以构造一个委托将传入的项目添加到列表中,并将该委托传递给所涉及到的每个集合,从而避免了每个集合为其项目构建一个新的List<T>
实例的需要。
引用 ReSharper 关于代码检查的文档:返回类型可以是 IEnumerable<T>
:
如果方法返回更通用的类型,它允许更大的灵活性。一方面,调用方法可以将
IEnumerable<T>
类型的值转换为许多其他集合类型或与 LINQ 一起使用。另一方面,开发人员可能能够更改方法的实现而无需更改返回类型。此外,返回更通用的类型可能有助于未来,例如,如果您决定将返回值更改为更具体的类型,例如
List<T>
:如果调用者期望IEnumerable<T>
,他们将能够接受List<T>
,但反之则不行。请注意,虽然这种替换并非总是可能。 如果在解决方案中的任何地方使用派生类型的方法,则 ReSharper 不会发出此建议。
我的观点是类型 IEnumerable<T>
具有强烈的deferred execution 语义,这可能会促使调用者将可枚举序列材料化为 T[]
或 List<T>
,以防需要多次枚举序列。材料化已经由基础数组或列表支持的序列是浪费的,因此我强烈反对 ReSharper 的这个具体建议。我更喜欢返回不通常与延迟执行相关联的接口,如 IReadOnlyCollection<T>
、ICollection<T>
、IReadOnlyList<T>
或 IList<T>
。
相关的 Microsoft 规则: CA1851:可能多次枚举 IEnumerable
集合。
由于调用foo的函数会执行 IEnumerable<T>.ToList()
,因此很可能该函数的目的不仅是遍历对象集合,而且还要对其进行处理(例如排序、修改某些元素等)。IEnumerable 用于只需要遍历的对象集合。因此,您应该保留List而不将其更改为IEnumerable。更多有用的信息在这里
IReadOnlyList
,以指示它们不应该修改它。 - AlexICollection<T>
。通常最好返回可能的最不具体的接口。 - Erik Funkenbusch