我应该使用像IEnumerable这样的接口,还是像List<>这样的具体类?

4

我最近在其他地方表达了我的观点,但我认为它值得进一步分析,所以我将其作为自己的问题发布。

假设我需要在程序中创建和传递一个容器。至少在这个阶段,我可能对一种容器与另一种容器没有强烈的意见,但我会选择其中一种;为了论证,我们假设我要使用一个 List<>

问题是:编写方法时,是否更好接受和返回高级接口(例如C#的 IEnumerable)?还是应该编写方法来接受和传递我选择的特定容器类。

我应该寻找哪些因素和标准来决定?哪种类型的程序可以从其中一种或另一种中受益?计算机语言是否影响您的决策?性能?程序大小?个人风格?

(这真的很重要吗?)

**(作业:找到它。但在寻找我的答案之前,请在此处发布您的答案,以免被偏见影响。)*

6个回答

16

你的方法应该始终接受执行其功能所需的最不具体类型。如果你的方法需要枚举,则应接受 IEnumerable。如果它需要执行 IList<> 特定的操作,根据定义,你必须给它一个 IList<>


同意,但我正要问几乎完全相同的问题,因为我的代码使用IEnumerable感觉很奇怪;而不是List<Foo> foos = fooQuery.GetFoos(),它变成了List<Foo> foos = new List<Foo>(fooQuery.GetFoos())。我错过了什么吗?或者这就是我们为灵活性付出的代价? - si618
使用上述注释的一个例子是在单元测试中,您想要验证结果,例如IList.Count,实际使用并不真正需要IList,IEnumerable就足够了,但这只是感觉很尴尬... - si618
@Si 我认为我们在这里谈论的是不同的事情。这个问题只涉及方法内部的范围;而你所说的是返回类型和方法外部的使用。 - Rex M

5

唯一应该影响您决策的是您计划如何使用参数。如果只需迭代它,请使用 IEnumerable<T>。如果要访问索引成员(例如 var x = list[3])或以任何方式修改列表(例如 list.Add(x)),请使用 ICollection<T>IList<T>


3

总是有取舍。一般的经验法则是尽可能在层次结构中声明高级别的事物。因此,如果您只需要访问IEnumerable中的方法,则应使用它。

另一个最近的例子是一个C API,它接受文件名而不是File *(或文件描述符)。在那里,文件名严重限制了可以传递的事物的类型(使用文件描述符可以传递许多事物,但只有一个具有文件名)。

一旦您必须开始转换,要么您已经走得太高了,要么您应该创建一个接受更具体类型的第二个方法。

我能想到的唯一例外情况是当速度绝对必要时,您不想经过虚拟方法调用的开销。声明特定类型会消除虚函数的开销(这将取决于语言/环境/实现,但作为一般语句,这很可能是正确的)。


3
这是我和Euro Micelli讨论后得出的答案,我认为Linq to Objects已经为这个问题提供了很好的答案。通过使用最简单的接口来处理一系列项,它提供了最大的灵活性,允许进行惰性生成,提高生产效率而不会牺牲性能(在任何实际意义上都不会)。尽管过早地进行抽象可能会带来成本,但主要是发现/发明新的抽象的成本。但如果你已经拥有完美的抽象,则疯狂地不利用它们就是愚蠢的,而泛型集合接口正是为此提供了便利。
有些人会告诉你,将类中的所有数据都公开可能更容易,以防需要访问它们。同样,Euro建议最好使用一个丰富的容器接口,例如IList(甚至具体类List),然后稍后清理混乱。
但是,我认为就像隐藏您不想访问的类的数据成员一样,以便稍后轻松修改该类的实现一样,您应该使用可用的最简单接口来引用一系列项。在实践中,先公开一些简单和基本的东西,然后再“放松”它,比从松散的东西开始并努力对其进行排序要容易得多。
因此,假设IEnumerable足以表示序列。然后,在需要添加或删除项目(但仍不需要按索引查找)的情况下,请使用IContainer,它继承了IEnumerable,因此可以与其他代码完全互操作。
这样,仅通过查看某些代码的本地内容,就可以清楚地知道该代码将能够处理哪些数据。
小程序确实需要较少的抽象。但是,如果它们成功了,它们往往会变成大程序。如果一开始就使用简单的抽象,这将更加容易。

0

这确实很重要,但正确的解决方案完全取决于使用情况。如果您只需要进行简单的枚举,那么可以使用IEnumerable,这样您就可以传递任何实现者来访问所需的功能。但是,如果您需要列表功能,并且不想在每次调用方法时都创建一个新的列表实例,那么请使用列表。


0

我在这里回答了一个类似的C#问题here。我认为你应该尽量提供最简单的合约,对于集合来说,我个人认为通常是IEnumerable Of T。

实现可以由内部的BCL类型提供 - 无论是Set、Collection、List等等 - 这些类型的必需成员由你的类型公开。

你的抽象类型总是可以继承简单的BCL类型,这些类型由你的具体类型实现。我认为这样做可以更容易地遵循LSP


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