这样的问题的答案:List<T> or IList<T>似乎总是同意返回一个接口比返回一个集合的具体实现更好。但我正在努力解决这个问题。实例化接口是不可能的,因此如果您的方法返回一个接口,它实际上仍然返回一个特定的实现。我通过编写两个小方法来尝试一下这个:
public static IList<int> ExposeArrayIList()
{
return new[] { 1, 2, 3 };
}
public static IList<int> ExposeListIList()
{
return new List<int> { 1, 2, 3 };
}
在我的测试程序中使用它们:
static void Main(string[] args)
{
IList<int> arrayIList = ExposeArrayIList();
IList<int> listIList = ExposeListIList();
//Will give a runtime error
arrayIList.Add(10);
//Runs perfectly
listIList.Add(10);
}
在我尝试添加新值的两种情况下,我的编译器都没有给出任何错误,但显然当我尝试向其中添加内容时,将我的数组公开为IList<T>
的方法会产生运行时错误。
因此,那些不知道我的方法中发生了什么,并且必须向其添加值的人,不得不首先将我的IList
复制到List
,以便能够添加值而不会有错误。当然,他们可以做一个类型检查,看看他们是在处理List
还是Array
,但是如果他们不这样做,他们想要将项目添加到集合中他们没有其他选择可以将IList
复制到List
,即使它已经是List
。数组是否永远不会公开为IList
?
我的另一个问题是基于linked question(强调我的)的接受答案:
如果您通过其他人将使用的库公开您的课程,您通常希望通过接口而不是具体实现来公开它。如果您决定更改课程的实现,这将有所帮助后来使用不同的具体类。在这种情况下,您的库的用户将不需要更新他们的代码,因为接口不会更改。
如果你只是在内部使用它,你可能不在乎,使用List可能没问题。
想象一下,实际上有人使用我IList<T>
方法获得的ExposeListIlist()
来添加/删除值。一切正常。但现在就像答案所暗示的那样,因为返回一个接口更灵活,我返回一个数组而不是一个List(在我这边没问题!),然后他们就可以了... ...
TLDR:
1)暴露界面会导致不必要的演员表?这没关系吗?
2)有时,如果库的用户不使用强制转换,那么当您更改方法时,它们的代码可能会中断,即使方法仍然完好无损。
我可能已经过度思考了这个问题,但我没有得到普遍的共识,即返回一个接口比返回一个实现更受欢迎。
答案 0 :(得分:107)
也许这不是直接回答你的问题,但在.NET 4.5+中,我更喜欢在设计公共或受保护的API时遵循这些规则:
IEnumerable<T>
; IReadOnlyCollection<T>
; IReadOnlyList<T>
,如果枚举,则可以使用项目计数和索引访问; ICollection<T>
; IList<T>
,如果枚举,项目计数,索引访问和修改可用。最后两个选项假定,该方法不能将数组作为IList<T>
实现返回。
答案 1 :(得分:31)
不,因为消费者应该知道IList究竟是什么:
IList是ICollection接口的后代,是基础 所有非通用列表的接口。 IList实现属于 三类:只读,固定大小和可变大小。一个 只读IList无法修改。固定大小的IList不允许 添加或删除元素,但它允许修改 现有要素。可变大小的IList允许添加,删除, 和修改元素。
您可以查看IList.IsFixedSize
和IList.IsReadOnly
并使用它做您想做的事。
我认为IList
是一个胖接口的示例,它应该被拆分为多个较小的接口,并且当您将数组作为IList
返回时,它也会违反Liskov substitution principle。< / p>
Read more如果你想做出关于返回接口的决定
<强>更新强>
挖掘更多内容,我发现IList<T>
未实现IList
,IsReadOnly
可通过基接口ICollection<T>
访问,但IsFixedSize
没有IList<T>
1}}。详细了解why generic IList<> does not inherit non-generic IList?
答案 2 :(得分:12)
与所有&#34;界面与实施&#34;问题,你必须意识到暴露公共成员意味着什么:它定义了这个类的公共API。
如果您将List<T>
公开为成员(字段,属性,方法......),则告诉该成员的使用者:通过访问此方法获得的类型是一个List<T>
,或者从中得到的东西。
现在,如果您公开界面,则隐藏&#34;实施细节&#34;您的班级使用具体类型。当然,您无法实例化IList<T>
,但您可以使用Collection<T>
,List<T>
,其派生或您自己的类型IList<T>
。
实际问题是&#34;为什么Array
实施IList<T>
&#34; 或&#34 ;为什么IList<T>
接口有这么多成员&#34; 。
这还取决于您希望该成员的消费者做什么。如果您实际通过Expose...
成员返回内部成员,则无论如何都要返回new List<T>(internalMember)
,否则消费者可以尝试将其转换为IList<T>
并修改您的通过那个内部成员。
如果您希望消费者迭代结果,请改为展示IEnumerable<T>
或IReadOnlyCollection<T>
。
答案 3 :(得分:8)
请注意脱离背景的一揽子报价。
返回接口比返回具体实现更好
如果它在SOLID principles的上下文中使用,则该引用才有意义。有5条原则,但为了讨论的目的,我们只谈谈最后的3条。
应该“依赖于抽象。不要依赖于结核。“
在我看来,这个原则是最难理解的。但是如果仔细看一下这个引用,它看起来很像你原来的引用。
依赖于接口(抽象)。不依赖于具体的实现(具体结果)。
这仍然有点令人困惑,但如果我们开始将其他原则应用于一起,它就会开始变得更有意义。
“程序中的对象应该可以替换为其子类型的实例,而不会改变该程序的正确性。”
正如您所指出的,返回Array
显然与返回List<T>
的行为不同,即使它们都实现了IList<T>
。这肯定违反了LSP。
要认识到的重要一点是接口是关于消费者的。如果您要返回一个接口,那么您已经创建了一个合同,可以使用该接口上的任何方法或属性而不改变程序的行为。
“许多特定于客户端的接口优于一个通用接口。”
如果您要返回接口,则应返回实现支持的最客户端特定接口。换句话说,如果您不希望客户端调用Add
方法,则不应返回带有Add
方法的接口。
不幸的是,.NET框架中的接口(特别是早期版本)并不总是理想的客户端特定接口。虽然@Dennis在他的回答中指出,但.NET 4.5 +中有更多选择。
答案 4 :(得分:4)
返回接口不一定比返回集合的具体实现更好。您应始终有充分的理由使用接口而不是具体类型。在你的例子中,这样做似乎毫无意义。
使用界面的有效理由可能是:
您不知道返回接口的方法的实现是什么样的,并且随着时间的推移可能会有很多。可能是其他人从其他公司写的。所以你只想就必需品达成一致,并让他们了解如何实现这些功能。
您希望以类型安全的方式公开一些独立于类层次结构的常用功能。应提供相同方法的不同基类型的对象将实现您的界面。
有人可能会说1和2基本上是相同的原因。它们是两种不同的场景,最终会产生相同的需求。
“这是合同”。如果合同是您自己的,并且您的应用程序在功能和时间上都是关闭的,那么使用界面通常没有意义。