像这样的问题的答案:List
public static IListExposeArrayIList() { return new[] { 1, 2, 3 }; } public static IList ExposeListIList() { return new List { 1, 2, 3 }; }
并在我的测试程序中使用它们:
static void Main(string[] args) { IListarrayIList = ExposeArrayIList(); IList listIList = ExposeListIList(); //Will give a runtime error arrayIList.Add(10); //Runs perfectly listIList.Add(10); }
在这两种情况下,当我尝试添加新值时,我的编译器没有给我任何错误,但显然IList
当我尝试向其添加内容时,将我的数组公开为暴露的方法会产生运行时错误.因此,那些不知道我的方法中发生了什么,并且必须为其添加值的人,被迫首先将我复制IList
到a List
以便能够添加值而不会有错误.当然,他们可以做一个类型检查,看看他们是否正在处理a List
或者a Array
,但是如果他们不这样做,并且他们想要将项目添加到集合中,他们别无选择将其复制IList
到a List
,即使它已经是一个List
.数组永远不会暴露为IList
?
我的另一个问题是基于相关问题的接受答案(强调我的):
如果您通过其他人将使用的库公开您的类,您通常希望通过接口而不是具体实现来公开它.如果您决定稍后更改类的实现以使用其他具体类,这将有所帮助.在这种情况下,您的库的用户将不需要更新他们的代码,因为接口不会更改.
如果你只是在内部使用它,你可能不在乎,使用List可能没问题.
想象一下有人实际上使用了我IList
从他的ExposeListIlist()
方法中获得的,就像添加/删除值一样.一切正常.但现在就像答案所暗示的那样,因为返回一个接口更灵活,我返回一个数组而不是一个List(在我这边没问题!),然后他们正在接受治疗......
TLDR:
1)暴露界面会导致不必要的演员表?这没关系吗?
2)有时,如果库的用户不使用强制转换,那么当您更改方法时,它们的代码可能会中断,即使该方法仍然完好无损.
我可能正在过度思考这个问题,但我并不认为返回接口比返回实现更受欢迎.
也许这不是直接回答你的问题,但在.NET 4.5+中,我更喜欢在设计公共或受保护的API时遵循这些规则:
IEnumerable
如果只有枚举可用,则返回;
IReadOnlyCollection
如果枚举和项目数都可用,则返回;
返回IReadOnlyList
,如果枚举,项目计数和索引访问可用;
ICollection
如果枚举,项目计数和修改可用,则返回;
返回IList
,如果枚举,项目计数,索引访问和修改可用.
最后两个选项假设,该方法不能将数组作为IList
实现返回.
不,因为消费者应该知道IList到底是什么:
IList是ICollection接口的后代,是所有非泛型列表的基本接口.IList实现分为三类:只读,固定大小和可变大小.无法修改只读IList.固定大小的IList不允许添加或删除元素,但它允许修改现有元素.可变大小的IList允许添加,删除和修改元素.
您可以检查IList.IsFixedSize
并IList.IsReadOnly
做你想做的事情是什么.
我认为IList
是一个胖接口的例子,它应该被拆分成多个较小的接口,并且当你将一个数组作为一个数组返回时它也违反了Liskov替换原则IList
.
如果您想决定返回界面,请阅读更多内容
UPDATE
挖掘更多,我发现,IList
没有实现IList
和 IsReadOnly
通过基座接口进行访问ICollection
,但没有IsFixedSize
进行IList
.阅读更多有关通用IList <>不继承非通用IList的原因?
与所有"接口与实现"问题一样,您必须意识到暴露公共成员意味着什么:它定义了此类的公共API.
如果您公开一个List
成员(字段,属性,方法,...),您告诉该成员的消费者:通过访问此方法获得的类型是一个List
或从中派生的东西.
现在,如果您公开接口,则使用具体类型隐藏类的"实现细节".当然,你不能实例化IList
,但可以使用Collection
,List
,衍生或其自己的类型实施IList
.
在实际的问题是:"为什么不Array
执行IList
",或"为什么有IList
接口,因此许多成员".
它还取决于您希望该成员的消费者做什么.如果您实际上通过您的Expose...
成员返回内部成员,您new List
仍然希望返回一个,否则消费者可以尝试将其强制转换为IList
内部成员并通过该成员修改内部成员.
如果您只是希望消费者迭代结果,则公开IEnumerable
或IReadOnlyCollection
代替.
小心使用脱离上下文的一揽子引用.
返回接口比返回具体实现更好
如果它在SOLID原则的上下文中使用,那么这个引用才有意义.有5个原则,但为了讨论的目的,我们只谈谈最后3个.
一个应该"取决于抽象.不要依赖于结核."
在我看来,这个原则是最难理解的.但是如果仔细看一下这个引用,它看起来很像你原来的引用.
取决于接口(抽象).不依赖具体实现(具体结果).
这仍然有点混乱,但如果我们开始将其他原则应用于一起,它就会开始变得更有意义.
"程序中的对象应该可以替换为其子类型的实例,而不会改变该程序的正确性."
正如你所指出的那样,即使他们都实现了,返回一个Array
明显不同于返回a的行为.这肯定违反了LSP.List
IList
要认识到的重要一点是接口是关于消费者的.如果您要返回一个接口,那么您已经创建了一个契约,该接口可以使用该接口上的任何方法或属性,而无需更改程序的行为.
"许多客户端特定的接口比一个通用接口更好."
如果您要返回接口,则应返回实现支持的最客户端特定接口.换句话说,如果您不希望客户端调用该Add
方法,则不应返回带有Add
方法的接口.
不幸的是,.NET框架中的接口(特别是早期版本)并不总是理想的客户端特定接口.虽然@Dennis在他的回答中指出,但.NET 4.5+中有更多选择.