我的服务应该返回哪个接口?IQueryable、IList、IEnumerable?

9

假设我有一个SearchService层,其中有一个方法可以搜索以某个字符串开头的所有汽车;

public static class Searcher{
    public IAnInterface<Car> CarsStartingWith(string startWith){
        //magic
    }
}

我的服务应该使用什么接口?
在我的应用程序中,IQueryable 可以呈现出一个漂亮流畅的界面。
IEnumerable 具有懒惰的特点。
IList 是最实用的。

为了保持一致性,我希望所有的服务都返回相同的接口,这样整个过程会更容易。
ICollection 也可能是一个选项,但它提供的功能太少了...


几乎重复的讨论在这里:https://dev59.com/RXRC5IYBdhLWcg3wJNmf - George Mauer
7个回答

3
我会选择 IEnumerable,因为它在框架中拥有更为核心的地位,为那些需要它的人提供了多样性,同时也为那些尚未深入学习LINQ等内容的人提供了熟悉感。

我喜欢IEnumerable,只要有一种方法可以给它一个Count方法就好了。我知道不知道有多少个元素是懒惰的本质,但Count属性是你需要这么多次的东西! - Boris Callens
LINQ为IEnumerable提供了一个Count扩展方法。 - Jeff Yates
@Jeff,如果原始对象是IQueryable(它将被检索和计数),那么这不是一个好主意。 - eglasius
1
那是正确的,但那不是所问的。任何延迟执行都会使此操作变得困难。无论接口如何,任何计数都可能进行检索,因此我认为您的观点在需要计数属性的情况下是无关紧要的。 - Jeff Yates

3

我的经验法则如下:

如果有机会将常规算法的核心重构为可以使用yield returns的方式,我会选择IEnumerable<T>。例如,如果我的当前实现在内部使用数组或List<T>,但我知道,至少从理论上讲,我可能想要并且能够重新设计它以进行惰性求值,那么我就会返回IEnumerable<T>。

我发现,返回IEnumerable<T>所获得的收益绝对值得使用它所带来的麻烦。

然而,如果算法在其本质上需要完全评估结果后才能返回(这种情况很少,但确实会发生),我会选择IList<T>。基本上,如果我已经计算了它,我就会返回它。IList<T>实现了IEnumerable<T>,因此所有与LINQ相关的用例仍然有效,但是你失去了惰性求值。如果我已经被迫提前评估,那就没有问题了。

我很少返回IQueryable。我使用这个接口的唯一时机是直接创建一个可查询的数据访问层或类似的东西。大多数情况下,使用它的开销不值得收益。

然而,如果你的目标是始终使用单个接口(我不一定同意这个目标),那么我会坚持使用IEnumerable<T>。


2
如果您希望所有服务都返回相同的接口,则我可能会选择使用 IEnumerable<>
如果需要,您的调用者可以轻松转换为 IQueryable 或创建一个 List
IEnumerable<Car> cars = Searcher.CarsStartingWith("L");
var carsList = cars.ToList();
var queryableCars = cars.AsQueryable();

完全同意。您应始终使用所需的最小合同。IEnumerable<T>可以被许多BCL类型消耗或转换,正如您所演示的那样。 - Ed Blackburn
我决定返回IEnumerable。如果在特定情况下IList更合适,我会使用命名约定使这些方法脱颖而出。 - Boris Callens
5
返回IEnumerable<T>的问题在于,如果您决定将其转换为IQuerable<T>,则不再是针对数据库进行查询,而是针对内存中的对象进行查询。正如James Curran指出的那样,IQueryable有点笨重,但在大多数场景中,我发现提供可查询集合返回更好,让使用者自行决定是否将其向下转换为IEnumerable - one.beat.consumer

2

IQueryable 有一个相当严格的要求——例如,您不能返回数组。

对我来说,通常在 IEnumerableIList 之间的选择最终取决于标准情况下哪个更容易实现。


到目前为止,我也是这样的。但我正在努力使我的编程风格更加一致化。我想加入IQueryable,因为流畅的接口通常看起来非常易读。 - Boris Callens

2

简短回答:这取决于具体情况。

详细回答:返回客户端代码需要的最丰富类型。如果不需要延迟加载,大多数情况下IList都可以使用。您仍然可以使用Linq查询IList或IEnumerable。如果需要延迟加载,则请使用IEnumerable或IQueryable。

附注:为所有服务返回相同的接口可能看起来很高尚,但考虑到不同的客户端使用模式,您可能需要返回不同的接口。


可能根据惰性/非惰性,使用命名约定会很有趣。 - Boris Callens
我会持不同意见。IList 提供的功能远比 IEnumerable 多,因此你应该选择 IEnumerable,无论是否需要惰性计算,除非你需要额外的方法,例如 Add、Remove 和索引。 - Jeff Yates

2

除非有严重的理由不这样做,否则始终使用 IEnumerable。然后可以使用 yield return 实现 getter。

IQueryable 是完全不同的东西。不是你随便实现作为典型的内存容器的替代品。

在其余的内容中,IEnumerable 和其他内容之间存在一个重要的区别:它是只读的。


将返回值转换为IEnumerable并不能使其只读:调用者仍可以将其转换回来。如果你希望只读,请在返回之前将结果转换为只读集合(例如使用List(T).AsReadOnly)。我通常返回IList(T)或ICollection(T),因为调用者经常需要计数。 - Joe
1
当然 - 我的意思是它是静态类型的,因此只读。你可能会说“没有必要将字段设置为私有 - 他们可以使用反射来访问它们”。 - Daniel Earwicker

0

如果您使用IQueryable作为返回类型,那么您的服务层就会有泄漏的抽象。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接