在规划我的程序时,我经常从一连串的想法开始:
足球队只是一个足球运动员名单。因此,我应该代表它:
var football_team = new List<FootballPlayer>();
此列表的顺序表示球员在名单中列出的顺序。
但我后来才意识到,除了仅仅是球员名单之外,球队还有其他属性,必须记录下来。例如,本赛季的总得分,当前预算,统一颜色,代表球队名称的string
等。
那么我认为:
好吧,足球队就像一个球员名单,但另外,它有一个名字(一个
string
)和一个运行的总分(一个int
)。 .NET 没有提供用于存储足球队的类,所以我将创建自己的类。最相似和相关的现有结构是List<FootballPlayer>
,所以我将继承它:class FootballTeam : List<FootballPlayer> { public string TeamName; public int RunningTotal }
但事实证明, 指南说你不应该继承List<T>
。我在两个方面完全被这个指南搞糊涂了。
显然, List
以某种方式针对性能进行了优化 。怎么会这样?如果我扩展List
会导致哪些性能问题?究竟会打破什么?
我看到的另一个原因是List
是由 Microsoft 提供的,我无法控制它,因此在暴露 “公共 API” 后我无法在以后更改它 。但我很难理解这一点。什么是公共 API,我为什么要关心?如果我当前的项目没有并且不太可能拥有此公共 API,我可以放心地忽略此指南吗?如果我继承List
,结果我需要一个公共 API,我会遇到什么困难?
为什么它甚至重要?列表是一个列表。什么可能改变?我可能想要改变什么?
最后,如果微软不希望我继承List
,为什么他们不让这个类sealed
?
显然,对于自定义集合,Microsoft 提供了一个Collection
类,应该扩展而不是List
。但是这个类非常简单,并且没有很多有用的东西, 比如AddRange
。 jvitor83 的答案提供了该特定方法的性能原理,但缓慢的AddRange
如何不比没有AddRange
?
继承Collection
是比继承List
更多的工作,我认为没有任何好处。当然微软不会告诉我无缘无故地做额外的工作,所以我不禁觉得我在某种程度上误解了一些东西,继承Collection
实际上并不是解决我问题的正确方法。
我见过像IList
这样的建议。就是不行。这是几十行样板代码,没有任何帮助。
最后,有些人建议将List
包装成:
class FootballTeam
{
public List<FootballPlayer> Players;
}
这有两个问题:
它使我的代码不必要地冗长。我现在必须调用my_team.Players.Count
而不仅仅是my_team.Count
。值得庆幸的是,使用 C#我可以定义索引器以使索引透明,并转发内部List
所有方法...... 但这是很多代码!我能为所有这些工作获得什么?
它只是简单没有任何意义。足球队没有 “拥有” 球员名单。这是球员名单。你没有说 “John McFootballer 加入了 SomeTeam 的球员”。你说 “约翰加入了 SomeTeam”。您不要在 “字符串的字符” 中添加字母,而是在字符串中添加字母。您不会将书籍添加到图书馆的书籍中,而是将图书添加到图书馆。
我意识到 “引擎盖下” 发生的事情可以说是 “将 X 添加到 Y 的内部列表中”,但这似乎是一种非常反直觉的思考世界的方式。
什么是表示数据结构的正确 C#方式,“逻辑上”(也就是说,“对人类的思维”)只是一个带有一些花里胡哨的things
的list
?
继承自List<T>
总是不可接受的?什么时候可以接受?为什么 / 为什么不呢?程序员在决定是否继承List<T>
时必须考虑什么?
这里有一些很好的答案。我会向他们添加以下几点。
什么是表示数据结构的正确 C#方式,“逻辑上”(也就是说,“对人类的思维”)只是一个带有一些花里胡哨的东西的列表?
问任何十个熟悉足球存在的非计算机程序员填写空白:
A football team is a particular kind of _____
有人说 “有一些花里胡哨的足球运动员名单”,还是他们都说 “运动队” 或“俱乐部” 或“组织”?你认为橄榄球队是一种特殊的球员名单的想法就在于你的人类思想和人类的思想。
List<T>
是一种机制 。 Football team 是一个业务对象 - 也就是代表程序业务领域中某个概念的对象。不要混合那些!足球队是一种团队; 它有一个名单,名单是一个球员名单 。名单不是一种特殊的球员名单 。名单是一个球员名单。因此,创建一个名为Roster
的属性,它是List<Player>
。除非你相信所有了解足球队的人都会从名单中删除球员,否则请将其设为ReadOnlyList<Player>
。
继承自
List<T>
总是不可接受的?
谁不能接受?我?没有。
什么时候可以接受?
当您构建扩展List<T>
机制的机制时 。
程序员在决定是否继承
List<T>
时必须考虑什么?
我是在构建机制还是业务对象 ?
但那是很多代码!我能为所有这些工作获得什么?
您花了更多的时间来输入您的问题,它会让您为List<T>
的相关成员编写转发方法五十次。你显然不害怕冗长,我们在这里讨论的代码非常少; 这是几分钟的工作。
我更多地考虑了一下,还有另一个原因就是不要将足球队的模型列为球员名单。事实上,将足球队建模为拥有球员名单可能是一个坏主意。团队作为 / 拥有一个玩家列表的问题在于,你所获得的是团队在某个时刻的快照 。我不知道你们这个班级的商业案例是什么,但是如果我有一个代表足球队的课程,我想问一下诸如 “有多少海鹰队球员在 2003 年至 2013 年期间因伤缺席比赛?” 之类的问题。或者 “之前为另一支球队效力的丹佛球员在场上的同比增幅最大?” 或者 “ 今年小猪队一路走了吗? ”
也就是说,在我看来,足球队很好地模仿了历史事实的集合,例如当一名球员被招募,受伤,退役等时。显然,当前的球员名单是一个重要的事实,应该是前面和 - 中心,但是对于需要更多历史视角的对象,您可能还有其他有趣的事情要做。
最后,有些人建议将 List 包装成:
这是正确的方法。 “不必要罗嗦” 是一种看待这个问题的坏方法。当你编写my_team.Players.Count
时,它有明确的含义。你想要算上球员。
my_team.Count
.. 什么也没有。算什么?
团队不是一个列表 - 不仅仅是一个玩家列表。一支球队拥有球员,所以球员应该成为其中的一员(成员)。
如果您真的担心它过于冗长,您可以随时公开团队中的属性:
public int PlayerCount {
get {
return Players.Count;
}
}
..which 变成:
my_team.PlayerCount
这现在有意义并且遵守得墨忒耳法则 。
您还应该考虑遵守复合重用原则 。通过继承List<T>
,你说团队是一个玩家列表,并暴露出不必要的方法。这是不正确的 - 正如你所说,一个团队不仅仅是一个球员名单:它有一个名字,经理,董事会成员,培训师,医疗人员,工资帽等。通过让你的团队成员包含一个球员名单,你 '说' 团队有一个球员名单 ',但它也可以有其他的东西。
哇,你的帖子有很多问题和要点。从微软获得的大多数推理都是恰到好处的。让我们从List<T>
一切开始
List<T>
经过高度优化。它的主要用途是用作对象的私有成员。 class MyList<T, TX> : List<CustomObject<T, Something<TX>> { ... }
: class MyList<T, TX> : List<CustomObject<T, Something<TX>> { ... }
。现在它就像做var list = new MyList<int, string>();
。 IList<T>
。这让你以后改变一个类中的实现。 Collection<T>
非常通用,因为它是一个通用的概念... 这个名字说明了一切; 它只是一个集合。有更精确的版本,如SortedCollection<T>
, ObservableCollection<T>
, ReadOnlyCollection<T>
等,每个版本都实现IList<T>
但不实现 List<T>
。 Collection<T>
允许覆盖成员(即 Add,Remove 等),因为它们是虚拟的。 List<T>
没有。 如果我正在编写此代码,那么类可能看起来像这样:
public class FootballTeam
{
// Football team rosters are generally 53 total players.
private readonly List<T> _roster = new List<T>(53);
public IList<T> Roster
{
get { return _roster; }
}
// Yes. I used LINQ here. This is so I don't have to worry about
// _roster.Length vs _roster.Count vs anything else.
public int PlayerCount
{
get { return _roster.Count(); }
}
// Any additional members you want to expose/wrap.
}