一尘不染

为什么不从 List<T> 继承?

c#

在计划我的程序时,我经常会从这样一个思路开始:

足球队只是足球运动员的名单。因此,我应该用以下方式表示它:

cs var football_team = new List<FootballPlayer>();

此列表的顺序代表球员在名册中列出的顺序。

但我后来意识到,除了球员名单之外,球队还有其他属性必须记录下来。比如本赛季总成绩、当前预算、球衣颜色、string代表球队名称的a等。

那么我想:

好吧,一个足球队就像一个球员名单,但另外,它有一个名字 (a string) 和一个总得分 (an int)。.NET 没有提供用于存储足球队的类,所以我将创建自己的类。最相似和最相关的现有结构是List<FootballPlayer>,所以我将从它继承:

cs class FootballTeam : List<FootballPlayer> { public string TeamName; public int RunningTotal }

但事实证明,一条准则说你不应该继承自List. 我在两个方面完全被这个指导方针弄糊涂了。

为什么不?

显然List以某种方式优化了性能。为何如此?如果我扩展会导致什么性能问题List?究竟会破坏什么?

我看到的另一个原因List是由 Microsoft 提供的,我无法控制它,所以在公开 “public API” 之后我无法更改它。但我很难理解这一点。什么是公共 API,我为什么要关心?如果我当前的项目没有也不太可能有这个公共 API,我可以安全地忽略这个指南吗?如果我确实继承自List 并且事实证明我需要一个公共 API,我会有什么困难?

为什么它甚至重要?一个列表就是一个列表。有什么可能改变?我可能想要改变什么?

最后,如果微软不想让我继承自List,他们为什么不创建这个类sealed

我还应该使用什么?

显然,对于自定义集合,Microsoft 提供了一个Collection应该扩展的类,而不是List. 但是这个类很简单,没有很多有用的东西,比如AddRange,为该特定方法提供了性能原理,但是慢AddRange不比不好怎么AddRange办?

继承 fromCollection比继承 from 要做更多的工作List,我认为没有任何好处。微软肯定不会无缘无故地告诉我做额外的工作,所以我不禁觉得我在某种程度上误解了一些东西,而继承Collection实际上并不是解决我问题的正确方法。

我已经看到了诸如实施之类的建议IList。就是不行。这是几十行样板代码,对我没有任何好处。

最后,有些人建议将其包装List在一些东西中:

class FootballTeam 
{ 
    public List<FootballPlayer> Players; 
}

这有两个问题:

  1. 它使我的代码不必要地冗长。我现在必须打电话my_team.Players.Count而不是仅仅打电话my_team.Count。值得庆幸的是,使用 C# 我可以定义索引器以使索引透明化,并转发内部的所有方法List……但那是很多代码!所有这些工作我能得到什么?
  2. 简单明了没有任何意义。足球队没有“拥有”球员名单。这球员名单。您不会说“John McFootballer 已加入 SomeTeam 的球员”。您说“John 已加入 SomeTeam”。您不会将字母添加到“字符串的字符”,而是将字母添加到字符串。您不会将一本书添加到图书馆的书籍中,而是将一本书添加到图书馆。

我意识到“幕后”发生的事情可以说是“将 X 添加到 Y 的内部列表中”,但这似乎是一种非常违反直觉的思考世界的方式。

我的问题(总结)

什么是表示数据结构的正确 C# 方式,“逻辑上”(也就是说,“对人的思想”)只是花里胡哨的一种listthings

继承自List<T>总是不可接受的吗?什么时候可以接受?为什么/为什么不?程序员在决定是否继承时必须考虑什么List<T>


阅读 151

收藏
2022-02-22

共1个答案

一尘不染

这里有一些很好的答案。我会向他们补充以下几点。

什么是表示数据结构的正确 C# 方式,“逻辑上”(也就是说,“对于人类思维”)只是一个带有一些花里胡哨的东西的列表?

请任何十个熟悉足球存在的非计算机程序员来填空:

足球队是一种特殊的____

有人说“花里胡哨的足球运动员名单”,还是都说“运动队”或“俱乐部”或“组织”?您认为足球队是特定类型的球员名单的想法存在于您的人类思想中,并且仅存在于您的人类思想中。

List<T>是一种机制。足球队是一个业务对象,即表示程序业务领域中的某个概念的对象。不要混合这些!足球队是一种球队;它有一个花名册,一个花名册是一个球员名单。名册不是特定类型的球员名单。名册球员的名单。因此,创建一个名为Rosterthat is a的属性List<Player>。并且ReadOnlyList<Player>当你在它的时候做它,除非你相信每个了解足球队的人都会从名单中删除球员。

继承自List<T>总是不可接受的吗?

谁不能接受?我?不。

什么时候可以接受?

当您构建扩展机制的List<T>机制时。

程序员在决定是否继承时必须考虑什么List<T>

我是在构建机制还是业务对象

但这是很多代码!所有这些工作我能得到什么?

List<T>您花了更多时间输入您的问题,因为您需要为50 次以上的相关成员编写转发方法。您显然不怕冗长,我们在这里谈论的是非常少量的代码;这是几分钟的工作。

更新

我对此进行了更多思考,还有另一个理由不将足球队建模为球员名单。事实上,将足球队建模为也有球员名单可能是个坏主意。球队作为/拥有球员名单的问题在于,你所拥有的是球队在某一时刻的快照。我不知道你对这门课的商业案例是什么,但如果我有一个代表足球队的课,我想问它“2003 年至 2013 年间有多少海鹰队球员因伤缺席比赛?”之类的问题。或“之前为另一支球队效力的丹佛球员中,哪位球员的码数同比增幅最大?” 或者“今年猪队一路走好吗?

也就是说,在我看来,一支足球队被很好地建模为历史事实的集合,例如球员何时被招募、受伤、退役等。显然,目前的球员名单是一个重要的事实,可能应该是前面和 -中心,但您可能还想对这个对象做一些其他有趣的事情,这些事情需要更多的历史视角。

2022-02-22