问答中心分类: LIST为什么不从 List 继承<T>?
0
匿名用户 提问 3小时 前

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

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

var football_team = new List();

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

但我后来意识到,除了球员名单之外,球队还有其他属性必须记录下来。例如,本赛季的总得分、当前预算、制服颜色、string代表团队的名称等。
那么我想:

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

class FootballTeam : List 
{ 
    public string TeamName; 
    public int RunningTotal 
}

但事实证明指导方针说你不应该继承自List.我在两个方面完全被这个指导方针弄糊涂了。
为什么不?
显然List以某种方式针对性能进行了优化.怎么会这样?如果我扩展会导致什么性能问题List?究竟会破坏什么?
我看到的另一个原因是List由微软提供,我无法控制它,所以在公开“公共 API”后,我以后无法更改它.但我很难理解这一点。什么是公共 API,我为什么要关心?如果我当前的项目没有也不太可能有这个公共 API,我可以安全地忽略这个指南吗?如果我确实继承自List 原来我需要一个公共 API,我会有什么困难?
为什么它甚至重要?一个列表就是一个列表。有什么可能改变?我可能想要改变什么?
最后,如果微软不想让我继承List,他们为什么不上课sealed?
我还应该使用什么?
显然,对于自定义集合,微软提供了一个Collection应该扩展的类而不是List.但是这个类很裸,没有很多有用的东西,AddRange, 例如。jvitor83 的回答为该特定方法提供了性能原理,但是如何缓慢AddRange不比不好AddRange?
继承自Collection比继承更多的工作List,我看不到任何好处。微软肯定不会无缘无故告诉我做额外的工作,所以我不禁觉得我在某种程度上误解了一些东西,并继承了Collection实际上不是我的问题的正确解决方案。
我已经看到了诸如实施之类的建议IList.就是不行。这是几十行样板代码,对我没有任何好处。
最后,有人建议包装List在某事:

class FootballTeam 
{ 
    public List Players; 
}

这有两个问题:

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

我意识到“幕后”发生的事情可以说是“将 X 添加到 Y 的内部列表”,但这似乎是一种非常违反直觉的思考世界的方式。
我的问题(总结)
什么是表示数据结构的正确 C# 方式,“逻辑上”(也就是说,“对人类思维”)只是一个listthings有一些花里胡哨的东西?
继承自List总是不能接受?什么时候可以接受?为什么/为什么不?程序员在决定是否继承时必须考虑什么List或不?

Flight Odyssey 回复 3小时 前

那么,您的问题真的是何时使用继承(Square is-a Rectangle,因为在请求通用 Rectangle 时提供 Square 总是合理的)以及何时使用组合(此外,一个 FootballTeam 有一个 FootballPlayers 列表其他与它一样“基本”的属性,比如名字)?如果其他程序员在期望一个简单的列表时在代码的其他地方传递了一个 FootballTeam,他们会感到困惑吗?

Flight Odyssey 回复 3小时 前

@FlightOdyssey 在实践中,我的问题似乎是“为什么在扩展List?” 我认为“球员名单”是足球队最基本的属性——最重要的是名称或预算。一个没有名字的球队可以存在(邻居的孩子组成两支球队在一个下午比赛) 但是没有球员的球队对我来说没有意义。至于惊讶,我不知道。我很难想象有人会对这样的继承感到惊讶,但我觉得我可能忽略了一些东西。

Flight Odyssey 回复 3小时 前

也可以看看stackoverflow.com/questions/4353203/…

Flight Odyssey 回复 3小时 前

如果我告诉你足球队是一家商业公司,它拥有一份球员合同列表,而不是拥有一些额外属性的球员名单?

Flight Odyssey 回复 3小时 前

这真的很简单。一个足球队是一个球员名单吗?显然不是,因为就像你说的,还有其他相关的属性。足球队有球员名单吗?是的,所以它应该包含一个列表。除此之外,组合通常比继承更可取,因为以后更容易修改。如果您发现继承和组合同时符合逻辑,请使用组合。

Flight Odyssey 回复 3小时 前

my_team.CountFootballs,my_team.CountSpareUniforms

Flight Odyssey 回复 3小时 前

@FlightOdyssey:小心正方形是一种矩形的类比;只有在 Square 和 Rectangle 是不可变的值.如果它们是可变的,那么你就有问题了。

Flight Odyssey 回复 3小时 前

@FlightOdyssey:假设您有一个 SquashRectangle 方法,它采用一个矩形,将其高度减半,并将其宽度加倍。现在传入一个矩形发生为 4×4,但类型为矩形,正方形为 4×4。调用 SquashRectangle 后对象的尺寸是多少?对于矩形,它显然是 2×8。如果正方形是 2×8,则它不再是正方形;如果不是 2×8,那么对相同形状的相同操作会产生不同的结果。结论是可变正方形是不是一种长方形。

Flight Odyssey 回复 3小时 前

@EricLippert 显然,Squash()应该只是一个虚拟方法Rectangle上课,你会得到帮助NotSupportedException当您尝试在Square那是伪装成Rectangle.为避免该异常,我将添加一个虚拟IsSquashable属性到类,从而将令人烦恼的异常转换为愚蠢的异常(因为他们应该刚刚检查了标志!)然后,我……哦不,我已经斗鸡眼了。 OO设计很难。

Flight Odyssey 回复 3小时 前

@EricLippert因此任何继承都不起作用,因为真正的子类总是关闭一些功能,只是因为它只是父类的一个子集。只是我们从来没有实现所有的功能,而且并不明显。所以,你的想法是荒谬的,是错误的。是的,这些不适合子类的函数必须被覆盖。

Flight Odyssey 回复 3小时 前

@Gangnus:您的陈述完全是错误的;一个真正的子类需要具有以下功能超集的超类。一个string需要做所有事情object可以做和更多.

Flight Odyssey 回复 3小时 前

@EricLippert 关于这一点足够公平RectangleSquares。我使用该特定示例的目的不是要在每一个可能的类结构Square应该作为一个子类来实现Rectangle,只是举一个简单的例子来说明继承的一般概念,而不会陷入扩展的解释和/或晦涩的术语中。

Flight Odyssey 回复 3小时 前

List有太多与您的业务无关的方法和属性。也许您可以将足球队声明为接口。基于列表的实现是一种可能的实现。

Flight Odyssey 回复 3小时 前

这里的答案提出了一个有效的观点(特别是参见 Eric Lippert 和 Sam Leach 的)。确实是足球队不是一个球员名单。这只是一方面。可以继承自的类List<T>这是有道理的,如果它的名字FootballPlayerCollection(纯粹主义者会纠正我Collection<T>是一个更好的选择,好的,但你明白了)。

Flight Odyssey 回复 3小时 前

有点题外话(但仍然值得一提恕我直言):List<T>不合适,因为它可以包含重复项。一个足球队真的可以不止一次拥有同一个球员吗?!如果是这样的话FootballTeam继承自或包含List<T>.我建议您查看具有更严格保证的集合类型,例如ISet<T>.

Flight Odyssey 回复 3小时 前

我不同意你列出的两个“问题”。首先,如果我有 1 支球队和 11 名球员,我希望 my_team.Players.Count = 11,Players.Count(一个数组) = 11 和 my_team.Count = 1(真的,my_team 甚至不应该有一个 Count 方法)。其次,一个足球队不仅仅是球员——它有各种属性,如建立在、状态等。因此,它更适合建模为 my_team,其中包括作为属性的球员。类 FootballTeam { 公开列表球员; } 是建模它的自然方式,即使 MS 不必给你指导。

Flight Odyssey 回复 3小时 前

哇,这里的答案是否偏离了轨道。建模总是很棘手,但真正的问题是何时以及何时不从 List 继承,以及为什么 MS 指南不建议这样做。请回答提出的问题。

Flight Odyssey 回复 3小时 前

“您不会将一本书添加到图书馆的书籍中,而是将一本书添加到图书馆”,您可能想看看“Meotnymy”是什么。

26 Answers
0
myermian 回答 3小时 前

哇,你的帖子有一大堆问题和要点。你从微软那里得到的大部分推理都是正确的。让我们从一切开始List<T>

  • List<T> 高度优化。它的主要用途是用作对象的私有成员。
  • Microsoft 没有密封它,因为有时您可能想要创建一个具有更友好名称的类:class MyList<T, TX> : List<CustomObject<T, Something<TX>> { ... }.现在就像做一样简单var list = new MyList<int, string>();.
  • CA1002:不公开通用列表:基本上,即使您打算将此应用程序用作唯一的开发人员,使用良好的编码实践进行开发也是值得的,因此它们会成为您的第二天性。您仍然可以将列表公开为IList<T>如果您需要任何消费者拥有索引列表。这使您可以稍后更改类中的实现。
  • 微软制造Collection<T>非常通用,因为它是一个通用概念……这个名字说明了一切;它只是一个集合。还有更精确的版本,例如SortedCollection<T>,ObservableCollection<T>,ReadOnlyCollection<T>等,每一个都实现IList<T>不是 List<T>.
  • Collection<T>允许成员(即添加、删除等)被覆盖,因为它们是虚拟的。List<T>才不是。
  • 你问题的最后一部分是正确的。足球队不仅仅是一个球员名单,所以它应该是一个包含球员名单的类。思考组合与继承.一个足球队球员名单(名单),它不是球员名单。

如果我正在编写这段代码,该类可能看起来像这样:

public class FootballTeam<T>//generic class
{
    // 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.
}
peterh 回复 3小时 前

FootballGroup : 列表,这对我来说似乎完全可以接受并且非常有效。没有解释任何真正的技术原因,为什么这种结构不值得生存,只有哲学论据。

Brian 回复 3小时 前

我对你的论点表示怀疑List未密封以允许更友好的名称。那是什么using别名是为了。

myermian 回复 3小时 前

@Brian:除了不能使用别名创建泛型之外,所有类型都必须是具体的。在我的例子中,List<T>被扩展为一个私有内部类,它使用泛型来简化List<T>.如果扩展仅仅是class FootballRoster : List<FootballPlayer> { },那么是的,我可以改用别名。我想值得注意的是,您可以添加其他成员/功能,但它是有限的,因为您不能覆盖List<T>.

Mark Hurd 回复 3小时 前

如果这是 Programmers.SE,我会同意当前的答案投票范围,但是,因为它是一个 SO 帖子,所以这个答案至少试图回答 C# (.NET) 特定问题,即使有明确的一般性设计问题。

myermian 回复 3小时 前

@jberger:我总是喜欢使用 LINQ 版本的Enumerable.Count<TSource>(IEnumerable<TSource>)因此,如果我更改了基础类型,只要它是IEnumerable<T>.与仅使用 List 相比,性能损失可以忽略不计。数数。不过有一天,我可能会决定我想要一种实现 IEnumerable 的新数据结构,但没有别的,谁知道呢。看stackoverflow.com/a/981283/347172了解更多信息。 🙂

Navin 回复 3小时 前

Collection<T> allows for members (i.e. Add, Remove, etc.) to be overridden现在是不从 List 继承的一个很好的理由。其他答案的哲学太多了。

Groo 回复 3小时 前

@Navin:这不适用于List<T>班级。Collection<T>类是应该被继承以便根据需要扩展其功能。

myermian 回复 3小时 前

@Groo:你确实意识到它不是从O(1) 到 O(n)如果序列是一种ICollection<T>或者ICollection.在这些情况下,LINQ 函数只是恢复为使用ICollection<T>.Count/ICollection.Count方法。

Groo 回复 3小时 前

@my:是的,对不起,那部分是正确的,我没有想到这一点。它还使用First,Last,以及其他可以从访问中受益的扩展方法ICollection直接成员,所以这样做实际上并没有太大的危害。

Jonathan Allen 回复 3小时 前

如果你写public IList<T> Roster消费者支付性能损失。使用 for-each 比使用 a 快得多List<T>比一个IList<T>,就像我的机器上的两倍多。

myermian 回复 3小时 前

@JonathanAllen:那不是foreach关键字作品(见stackoverflow.com/a/3679993/347172)。在这种情况下,基础类型仍然是List<T>这意味着它将调用GetEnumerator()在类型上并使用该枚举器进行迭代。

Jonathan Allen 回复 3小时 前

如果你打电话List<T>.GetEnumerator(), 你得到一个名为Enumerator.如果你打电话IList<T>.GetEnumerator(), 你得到一个类型的变量IEnumerable<T>恰好包含所述结构的盒装版本。在前一种情况下,foreach 将直接调用方法。在后者中,所有调用都必须通过接口虚拟分派,从而使每个调用变慢。 (在我的机器上大约慢两倍。)

myermian 回复 3小时 前

@JonathanAllen:任何呼叫GetEnumerator()将返回一个实现的类型IEnumerable<T>(或者IEnumerable) 取决于使用情况。虽然(在这种情况下)基础类型是相同的。它是Enumerator结构。请参阅参考来源:参考来源.microsoft.com/#mscorlib/system/collections/……它们都返回相同的类型。

Jonathan Allen 回复 3小时 前

你完全没有抓住重点。foreach不在乎是否Enumerator实现IEnumerable<T>界面。如果它可以找到它需要的方法Enumerator,那么它不会使用IEnumerable<T>.如果你真的反编译代码,你可以看到 foreach 在迭代时如何避免使用虚拟调度调用List<T>, 但会与IList<T>.

Jonathan Allen 回复 3小时 前

事实上,GetEnumerator 甚至不需要返回IEnumerable.您可以创建自己的具有正确方法但不实现该接口的枚举器,并且foreach仍然可以工作。

JAlex 回复 3小时 前

您需要指定T作为通用参数,public class FootballTeam<T> ...否则该领域List<T> _roster是无效的语法。

0
Nean Der Thal 回答 3小时 前
class FootballTeam : List<FootballPlayer> 
{ 
    public string TeamName; 
    public int RunningTotal;
}

以前的代码意思是:一群来自街头踢足球的人,他们碰巧有一个名字。就像是:
踢足球的人
无论如何,这段代码(来自我的回答)

public class FootballTeam
{
    // A team's name
    public string TeamName; 

    // Football team rosters are generally 53 total players.
    private readonly List<T> _roster = new List<T>(53);

    public IList<T> Roster
    {
        get { return _roster; }
    }

    public int PlayerCount
    {
        get { return _roster.Count(); }
    }

    // Any additional members you want to expose/wrap.
}

意思是:这是一支拥有管理层、球员、管理员等的足球队。比如:
图片显示了曼联队的成员(和其他属性)
这就是您的逻辑在图片中的呈现方式……

Ben 回复 3小时 前

我希望你的第二个例子是足球俱乐部,而不是足球队。足球俱乐部有经理和管理员等。从这个来源:“足球队是给一群人的统称球员…可以选择这样的球队与对方球队进行比赛,代表一个足球俱乐部……”。所以我认为一个球队玩家列表(或者更准确地说是玩家集合)。

SiD 回复 3小时 前

更优化private readonly List<T> _roster = new List<T>(44);

Davide Cannizzo 回复 3小时 前

需要导入System.Linq命名空间。

E. Verdi 回复 3小时 前

@Ben 在我看来,Nean Der Thal 的回答是正确的。一支足球队包含管理层(教练、助理等)、球员(以及一个重要说明:“球员被选中参加比赛”,因为并非球队中的所有球员都会被选中参加例如冠军联赛)、管理员等. 足球俱乐部就像办公室里的人、体育场、球迷、俱乐部董事会,最后但同样重要的是:多支球队(如一线队、女队、青年队等)不要相信维基百科所说的一切;)

Ben 回复 3小时 前

@E.Verdi 我认为第二张照片的背景是一个体育场,正如您所建议的那样,它使它看起来代表一个俱乐部而不仅仅是一支球队。归根结底,这些术语的定义只是语义(我可能称之为团队,其他人可能称之为名册等)。我认为重点在于,如何对数据进行建模取决于它的用途。这是一个很好的观点,我认为这些图片有助于展示这一点:)

0
Tim B 回答 3小时 前

这是一个经典的例子作品对比遗产.
在这种特定情况下:
团队是具有附加行为的玩家列表吗
或者
球队是否是它自己的对象,恰好包含一个球员名单。
通过扩展 List,您可以通过多种方式限制自己:

  1. 您不能限制访问(例如,阻止人们更改名册)。无论您是否需要/想要它们,您都可以获得所有 List 方法。
  2. 如果您还想拥有其他事物的列表,会发生什么。例如,球队有教练、经理、球迷、设备等。其中一些很可能本身就是列表。
  3. 您限制了继承的选择。例如,您可能想要创建一个通用的 Team 对象,然后让 BaseballTeam、FootballTeam 等继承自该对象。要从 List 继承,您需要从 Team 继承,但这意味着所有不同类型的团队都被迫具有该名册的相同实现。

组合 – 包括一个对象,它可以在对象内部提供您想要的行为。
继承 – 您的对象成为具有您想要的行为的对象的实例。
两者都有其用途,但这是一个明显的案例,其中组合更可取。

Cruncher 回复 3小时 前

扩展#2,列表没有意义有一个列表。

Mauro Sampietro 回复 3小时 前

首先,这个问题与组合与继承无关。答案是 OP 不想实现更具体的列表,所以他不应该扩展 List<>。我很扭曲,因为你在 stackoverflow 上的得分很高,应该清楚地知道这一点,人们相信你说的话,所以现在至少有 55 人投票赞成并且他们的想法很困惑,他们相信任何一种方式都可以构建一个系统,但显然不是! #无怒

Tim B 回复 3小时 前

@sam 他想要列表的行为。他有两个选择,他可以扩展列表(继承),或者他可以在他的对象中包含一个列表(组合)。也许你误解了问题或答案的一部分,而不是 55 个人错了,你是对的? 🙂

Mauro Sampietro 回复 3小时 前

@TimB ..我们真的在谈论同一件事吗?!en.wikipedia.org/wiki/Composition_over_inheritance

Tim B 回复 3小时 前

是的。这是一个退化的案例,只有一个 super 和 sub,但我对他的具体问题给出了更一般的解释。他可能只有一个团队,并且可能总是有一个列表,但选择仍然是继承该列表或将其作为内部对象包含在内。一旦您开始包含多种类型的球队(足球、板球等),并且他们开始不仅仅是一个球员名单,您就会看到您如何拥有完整的组成与继承问题。在这种情况下,着眼大局很重要,以避免及早出现错误的选择,这意味着以后需要进行大量的重构。

Andrew Rondeau 回复 3小时 前

这是一个组合与继承的问题。 OP 提出的主题最终表明,在他的思想中,他并不真正理解“is-a”和“has-a”关系之间的区别。 OP 还误解了行为与实现。

Julia McGuigan 回复 3小时 前

OP 没有要求组合,但用例是 X:Y 问题的明确示例,其中 4 个面向对象编程原则之一被破坏、误用或未完全理解。更明确的答案是编写一个专门的集合,如堆栈或队列(似乎不适合用例)或理解组合。足球队不是足球运动员名单。 OP是否明确要求它是无关紧要的,如果不了解抽象,封装,继承和多态性,他们将无法理解答案,因此,X:Y amok

0
Satyan Raina 回答 3小时 前

正如大家所指出的,一队球员不是球员名单。这个错误是由世界各地的许多人所犯的,也许在不同的专业水平上。通常问题是微妙的,有时非常严重,就像在这种情况下一样。这样的设计是不好的,因为它们违反了里氏替换原则.互联网上有很多很好的文章解释了这个概念,例如,http://en.wikipedia.org/wiki/Liskov_substitution_principle 总之,在类之间的父/子关系中需要保留两条规则:

  • 一个孩子不应该要求比完全定义父母的特征少。
  • 除了完全定义孩子的特征之外,父母不应该要求任何特征。

换句话说,父母是孩子的必要定义,孩子是父母的充分定义。
这是一种思考解决方案并应用上述原则的方法,可以帮助人们避免此类错误。应该通过验证父类的所有操作在结构上和语义上是否对派生类都有效来检验假设。

  • 足球队是足球运动员的名单吗? (列表的所有属性是否以相同的含义适用于团队)
    • 团队是同质实体的集合吗?是的,团队是玩家的集合
    • 包含球员的顺序是否描述了球队的状态,球队是否确保保留顺序,除非明确改变?不,不
    • 球员是否会根据他们在球队中的顺序位置被包括/放弃?不

如您所见,只有列表的第一个特征适用于团队。因此,团队不是列表。列表将是您如何管理团队的实现细节,因此它只能用于存储玩家对象并使用 Team 类的方法进行操作。
在这一点上,我想指出,在我看来,Team 类甚至不应该使用 List 来实现。在大多数情况下,它应该使用 Set 数据结构(例如 HashSet)来实现。

Fred Mitchell 回复 3小时 前

在 List 与 Set 上取得了不错的成绩。这似乎是一个太常见的错误。当集合中只能有一个元素的实例时,某种集合或字典应该是实现的首选候选者。有两个同名球员的球队是可以的。不能同时包含一个玩家两次。

Tony Delroy 回复 3小时 前

+1 有一些好处,尽管更重要的是要问“数据结构是否可以合理地支持所有有用的东西(理想情况下是短期和长期)”,而不是“数据结构做的比有用的多”。

Satyan Raina 回复 3小时 前

@TonyD好吧,我要提出的观点不是应该检查“数据结构是否比有用的更多”。它是检查“如果父数据结构做了一些与子类可能暗示的行为无关、无意义或违反直觉的事情”。

Tony Delroy 回复 3小时 前

@SatyanRaina:其中,能够做一些无关紧要或无意义的额外事情并没有错,只要它们实际上不损害有意义并将被使用的操作。例如,如果平衡二叉树碰巧以键排序的顺序迭代元素并且这不是必需的,那么它实际上也不是问题。例如“包含球员的顺序[被]描述了球队的状态”:如果不需要顺序,或者仍然可以有效地方便地形成可能需要的多个顺序,则特定的默认顺序没有害处。

Satyan Raina 回复 3小时 前

@TonyD 实际上存在从父级派生的不相关特征的问题,因为在许多情况下它会通过否定测试。程序员可以扩展 Human{ eat();跑(); write();} 来自大猩猩{ eat();跑(); swing();} 认为具有摆动能力的人没有任何问题。然后在游戏世界中,你的人类突然开始绕过所有假定的陆地障碍,只需在树上摆动即可。除非明确规定,实际的人类不应该能够摆动。这样的设计使 api 很容易被滥用和混淆

Tony Delroy 回复 3小时 前

@SatyanRaina:请不要将主题从理想 API 中的偶然行为更改为从根本上破坏派生。具体来说,您的“是包含顺序…”和“…根据它们的顺序位置包含/删除…”测试不是很好的测试不恰当对于列表,他们只是确定列表具有一些不需要的属性。您建议使用 HashSet ,这是一个合理的建议,但使用您的逻辑,我可以说“是否有必要通过键进行 O(1) 查找?不”,“尽管算法效率低下,性能测试还是通过了”。测试是一场军备竞赛。

Satyan Raina 回复 3小时 前

@TonyD 我也不建议 Player 类应该从 HashSet 派生。我建议 Player 类“在大多数情况下”应该通过 Composition 使用 HashSet 来实现,这完全是实现级别的细节,而不是设计级别的细节(这就是为什么我提到它作为我的答案的旁注)。如果这种实现有正当理由,它可以很好地使用列表来实现。因此,要回答您的问题,是否有必要通过按键进行 O(1) 查找?不,因此也不应该从 HashSet 扩展 Player。

0
Sam Leach 回答 3小时 前

如果FootballTeam有预备队和主队吗?

class FootballTeam
{
    List<FootballPlayer> Players { get; set; }
    List<FootballPlayer> ReservePlayers { get; set; }
}

你会如何建模呢?

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

关系很明确有个并不是是一个.
或者RetiredPlayers?

class FootballTeam
{
    List<FootballPlayer> Players { get; set; }
    List<FootballPlayer> ReservePlayers { get; set; }
    List<FootballPlayer> RetiredPlayers { get; set; }
}

根据经验,如果您想从集合中继承,请命名该类SomethingCollection.
请问您SomethingCollection在语义上有意义吗?仅当您的类型是一个收集Something.
如果是FootballTeam听起来不对。一个Team不仅仅是一个Collection.一个Team正如其他答案所指出的那样,可以有教练,培训师等。
FootballCollection听起来像是足球的集合,或者可能是足球用具的集合。TeamCollection,团队的集合。
FootballPlayerCollection听起来像是一组玩家,这将是继承自的类的有效名称List<FootballPlayer>如果你真的想这样做。
真的List<FootballPlayer>是一个非常适合处理的类型。也许IList<FootballPlayer>如果您从方法中返回它。
总之
问你自己

  1. X一个Y?或者 X一个Y?
  2. 我的班级名称是什么意思吗?
supercat 回复 3小时 前

如果每个玩家的类型都将其归类为属于DefensivePlayers,OffensivePlayers, 或者OtherPlayers, 拥有一个可以被代码使用的类型可能是合法有用的List<Player>但也包括成员DefensivePlayers,OffsensivePlayers, 或者SpecialPlayers类型IList<DefensivePlayer>,IList<OffensivePlayer>, 和IList<Player>.可以使用单独的对象来缓存单独的列表,但是将它们封装在与主列表相同的对象中似乎更干净[使用列表枚举器的失效…

supercat 回复 3小时 前

…作为主列表已更改并且子列表在下次访问时需要重新生成的事实的提示]。

sara 回复 3小时 前

虽然我同意所提出的观点,但看到有人提供设计建议并建议公开一个具体的列表,我真的很伤心在业务对象中使用公共 getter 和 setter 🙁