为什么不从List继承呢?

时间:2022-09-25 10:58:19

When planning out my programs, I often start with a chain of thought like so:

当我计划我的项目时,我经常从一连串的想法开始:

A football team is just a list of football players. Therefore, I should represent it with:

一个足球队只是一个足球运动员的名单。因此,我应该用:

var football_team = new List<FootballPlayer>();

The ordering of this list represent the order in which the players are listed in the roster.

这个列表的顺序表示球员在花名册上的顺序。

But I realize later that teams also have other properties, besides the mere list of players, that must be recorded. For example, the running total of scores this season, the current budget, the uniform colors, a string representing the name of the team, etc..

但我后来意识到,除了球员名单之外,球队还有其他必须被记录的属性。例如,本赛季的总得分,当前的预算,统一的颜色,代表球队名字的字符串等等。

So then I think:

所以我认为:

Okay, a football team is just like a list of players, but additionally, it has a name (a string) and a running total of scores (an int). .NET does not provide a class for storing football teams, so I will make my own class. The most similar and relevant existing structure is List<FootballPlayer>, so I will inherit from it:

好的,一个足球队就像一个球员列表,但是另外,它有一个名字(一个字符串)和一个连续的分数(一个int)。最相似和相关的现有结构是List <足球运动员> ,所以我将继承它:

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

But it turns out that a guideline says you shouldn't inherit from List<T>. I'm thoroughly confused by this guideline in two respects.

但事实证明,一个指导原则说你不应该继承List 。在这两个方面,我完全被这个指南搞糊涂了。

Why not?

Apparently List is somehow optimized for performance. How so? What performance problems will I cause if I extend List? What exactly will break?

显然,列表以某种方式优化了性能。所以如何?如果扩展列表,会导致什么性能问题?将什么?

Another reason I've seen is that List is provided by Microsoft, and I have no control over it, so I cannot change it later, after exposing a "public API". But I struggle to understand this. What is a public API and why should I care? If my current project does not and is not likely to ever have this public API, can I safely ignore this guideline? If I do inherit from List and it turns out I need a public API, what difficulties will I have?

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

Why does it even matter? A list is a list. What could possibly change? What could I possibly want to change?

这有什么关系呢?列表就是一个列表。什么能改变吗?我能改变什么呢?

And lastly, if Microsoft did not want me to inherit from List, why didn't they make the class sealed?

最后,如果微软不想让我继承List的话,为什么他们不把这个类封起来呢?

What else am I supposed to use?

Apparently, for custom collections, Microsoft has provided a Collection class which should be extended instead of List. But this class is very bare, and does not have many useful things, such as AddRange, for instance. jvitor83's answer provides a performance rationale for that particular method, but how is a slow AddRange not better than no AddRange?

显然,对于自定义集合,Microsoft提供了一个应该扩展而不是List的集合类。但是这个类非常简单,并且没有很多有用的东西,例如AddRange。jvitor83的答案为该特定方法提供了性能基础,但缓慢的AddRange与无AddRange有何不同?

Inheriting from Collection is way more work than inheriting from List, and I see no benefit. Surely Microsoft wouldn't tell me to do extra work for no reason, so I can't help feeling like I am somehow misunderstanding something, and inheriting Collection is actually not the right solution for my problem.

从集合中继承要比从List中继承工作量大得多,我看没有什么好处。当然,微软不会无缘无故地让我做额外的工作,所以我忍不住觉得我在某种程度上误解了什么,继承收藏并不是解决我问题的正确方法。

I've seen suggestions such as implementing IList. Just no. This is dozens of lines of boilerplate code which gains me nothing.

我看到了一些建议,比如实施IList。只是没有。这是几十行样板代码,我什么也得不到。

Lastly, some suggest wrapping the List in something:

最后,一些人建议将清单包装在一些东西上:

class FootballTeam 
{ 
    public List<FootballPlayer> Players; 
}

There are two problems with this:

这有两个问题:

  1. It makes my code needlessly verbose. I must now call my_team.Players.Count instead of just my_team.Count. Thankfully, with C# I can define indexers to make indexing transparent, and forward all the methods of the internal List... But that's a lot of code! What do I get for all that work?

    它使我的代码不必要地冗长。我现在必须呼叫my_team.Players。计数,而不是my_team.Count。值得庆幸的是,使用c#我可以定义索引器,使索引变得透明,并转发内部列表的所有方法……但那是很多的代码!做了那么多工作,我能得到什么?

  2. It just plain doesn't make any sense. A football team doesn't "have" a list of players. It is the list of players. You don't say "John McFootballer has joined SomeTeam's players". You say "John has joined SomeTeam". You don't add a letter to "a string's characters", you add a letter to a string. You don't add a book to a library's books, you add a book to a library.

    这是毫无意义的。一个足球队没有球员名单。这是球员名单。你不会说“约翰·麦克球员加入了某个队的球员”。你说“约翰加入了一个团队”。你不向“字符串的字符”添加一个字母,而是向字符串添加一个字母。你不是把一本书加到图书馆的书里,而是把一本书加到图书馆里。

I realize that what happens "under the hood" can be said to be "adding X to Y's internal list", but this seems like a very counter-intuitive way of thinking about the world.

我意识到,“在引擎盖下”发生的事情可以说是“把X加到Y的内部列表中”,但这似乎是一种非常反直觉的思考世界的方式。

My question (summarized)

What is the correct C# way of representing a data structure, which, "logically" (that is to say, "to the human mind") is just a list of things with a few bells and whistles?

c#表示数据结构的正确方式是什么,它“逻辑地”(也就是说,“对人类的思想”)仅仅是一个有一些铃铛和哨声的东西的列表?

Is inheriting from List<T> always unacceptable? When is it acceptable? Why/why not? What must a programmer consider, when deciding whether to inherit from List<T> or not?

继承List 总是不可接受的吗?什么时候可以接受吗?为什么/为什么不?当决定是否从List 继承时,程序员必须考虑什么?

25 个解决方案

#1


1107  

There are some good answers here. I would add to them the following points.

这里有一些很好的答案。我想补充以下几点。

What is the correct C# way of representing a data structure, which, "logically" (that is to say, "to the human mind") is just a list of things with a few bells and whistles?

c#表示数据结构的正确方式是什么,它“逻辑地”(也就是说,“对人类的思想”)仅仅是一个有一些铃铛和哨声的东西的列表?

Ask any ten non-computer-programmer people who are familiar with the existence of football to fill in the blank:

让任何十个熟悉足球存在的非电脑程序员来填补这个空白:

A football team is a particular kind of _____

Did anyone say "list of football players with a few bells and whistles", or did they all say "sports team" or "club" or "organization"? Your notion that a football team is a particular kind of list of players is in your human mind and your human mind alone.

有没有人说过“足球运动员名单上有一些花哨的东西”,或者他们都说“运动队”、“俱乐部”或“组织”?你认为足球队是一种特殊的球员名单,这一观点只存在于你的人类思维中,也存在于你的人类思维中。

List<T> is a mechanism. Football team is a business object -- that is, an object that represents some concept that is in the business domain of the program. Don't mix those! A football team is a kind of team; it has a roster, a roster is a list of players. A roster is not a particular kind of list of players. A roster is a list of players. So make a property called Roster that is a List<Player>. And make it ReadOnlyList<Player> while you're at it, unless you believe that everyone who knows about a football team gets to delete players from the roster.

列出< T >是一种机制。足球队是一个业务对象——即表示程序的业务领域中的某个概念的对象。不要把这些!足球队是一种球队;它有一个花名册,花名册是球员名单。花名册并不是一种特定的球员名单。花名册是一份球员名单。因此,创建一个名为列表的属性,它是一个列表 。如果你不相信每个知道一个足球队的人都可以把球员从名单中删除,那就把它重新列出来。

Is inheriting from List<T> always unacceptable?

继承List 总是不可接受的吗?

Unacceptable to who? Me? No.

无法接受谁?我吗?不。

When is it acceptable?

什么时候可以接受吗?

When you're building a mechanism that extends the List<T> mechanism.

当您构建扩展列表 机制的机制时。

What must a programmer consider, when deciding whether to inherit from List<T> or not?

当决定是否从List 继承时,程序员必须考虑什么?

Am I building a mechanism or a business object?

我构建的是机制还是业务对象?

But that's a lot of code! What do I get for all that work?

但那是很多的代码!做了那么多工作,我能得到什么?

You spent more time typing up your question that it would have taken you to write forwarding methods for the relevant members of List<T> fifty times over. You're clearly not afraid of verbosity, and we are talking about a very small amount of code here; this is a few minutes work.

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

UPDATE

I gave it some more thought and there is another reason to not model a football team as a list of players. In fact it might be a bad idea to model a football team as having a list of players too. The problem with a team as/having a list of players is that what you've got is a snapshot of the team at a moment in time. I don't know what your business case is for this class, but if I had a class that represented a football team I would want to ask it questions like "how many Seahawks players missed games due to injury between 2003 and 2013?" or "What Denver player who previously played for another team had the largest year-over-year increase in yards ran?" or "Did the Piggers go all the way this year?"

我给了它更多的思考,还有另外一个理由不把一个足球队当作球员名单。事实上,把一个足球队打造成一个球员名单也不是个好主意。球队的问题在于你所拥有的只是球队在某一时刻的快照。我不知道你的业务案例是对于这门课,但是如果我有一个类代表一个足球队我想问这样的问题“多少海鹰队球员因伤错过了游戏在2003年和2013年之间?”和“什么丹佛球员以前打了另一个团队的最大码了同比增长?”或“今年Piggers一路吗?”

That is, a football team seems to me to be well modeled as a collection of historical facts such as when a player was recruited, injured, retired, etc. Obviously the current player roster is an important fact that should probably be front-and-center, but there may be other interesting things you want to do with this object that require a more historical perspective.

即足球队我看来,建模为历史事实的集合,如当一个球员招募了,受伤了,退休了,等。显然目前的球员名单应该放在一个重要的事实,但可能还有其他有趣的事情你想做的这个对象需要更多的历史观点。

#2


227  

Lastly, some suggest wrapping the List in something:

最后,一些人建议将清单包装在一些东西上:

That is the correct way. "Needlessly wordy" is a bad way to look at this. It has an explicit meaning when you write my_team.Players.Count. You want to count the players.

这是正确的方法。“不必要地啰嗦”是一个不好的看法。当您编写my_team.Players.Count时,它具有明确的含义。你要数球员的数目。

my_team.Count

..means nothing. Count what?

. .没有任何意义。计算什么?

A team isn't a list - the consist of more than just a list of players. A team owns players, so players should be part of it (a member).

一个团队不是一个列表——它不仅仅是一个球员列表。球队拥有球员,所以球员应该是球队的一员。

If you're really worried about it being overly verbose, you can always expose properties from the team:

如果您真的担心它过于冗长,您可以始终公开来自团队的属性:

public int PlayerCount {
    get {
        return Players.Count;
    }
}

..which becomes:

. .这就变成:

my_team.PlayerCount

This has meaning now and adheres to The Law Of Demeter.

这现在有了意义,并且遵循了德墨忒耳定律。

You should also consider adhering to the Composite reuse principle. By inheriting from List<T>, you're saying a team is a list of players and exposing needless methods out of it. This is incorrect - as you stated, a team is more than a list of players: it has a name, managers, board members, trainers, medical staff, salary caps, etc. By having your team class contain a list of players, you're saying "A team has a list of players", but it can also have other things.

您还应该考虑遵守复合重用原则。通过继承List ,您是说一个团队是一个球员的列表,并从其中暴露出不必要的方法。这是不正确的,正如你所说,一个团队不仅仅是玩家的列表:它有一个名字,经理、董事会成员,运动鞋,医务人员、工资帽,等。通过你的团队类包含一个玩家列表,你说“一个团队的球员”,但它也可以有其他的事情。

#3


107  

Wow, your post has an entire slew of questions and points. Most of the reasoning you get from Microsoft is exactly on point. Let's start with everything about List<T>

哇,你的帖子里有很多问题和观点。你从微软得到的大部分推理都是正确的。让我们从List 开始。

  • List<T> is highly optimized. It's main usage is to be used as a private member of an object.
  • < T >是高度优化的列表。它的主要用法是作为对象的私有成员使用。
  • Microsoft did not seal it because sometimes you might want to create a class that has a friendlier name: class MyList<T, TX> : List<CustomObject<T, Something<TX>> { ... }. Now it's as easy as doing var list = new MyList<int, string>();.
  • 微软没有封印它,因为有时您可能想要创建一个更友好的类:class MyList : List >{…}。现在它就像使用var列表= new MyList ()一样简单; ,> ,>
  • CA1002: Do not expose generic lists: Basically, even if you plan to use this app as the sole developer, it's worthwhile to develop with good coding practices, so they become instilled into you and second nature. You are still allowed to expose the list as an IList<T> if you need any consumer to have an indexed list. This let's you change the implementation within a class later on.
  • CA1002:不要公开通用列表:基本上,即使您打算使用这个应用作为唯一的开发人员,使用良好的编码实践进行开发也是值得的,因此它们会逐渐灌输给您和您的第二天性。您仍然可以将列表作为IList 公开,如果您需要任何消费者有一个索引列表。让我们稍后在类中更改实现。
  • Microsoft made Collection<T> very generic because it is a generic concept... the name says it all; it is just a collection. There are more precise versions such as SortedCollection<T>, ObservableCollection<T>, ReadOnlyCollection<T>, etc. each of which implement IList<T> but not List<T>.
  • 微软使Collection 非常通用,因为它是一个通用的概念……名字就说明了一切;它只是一个集合。有更精确的版本,如SortedCollection , ObservableCollection , ReadOnlyCollection 等,每一个实现IList ,但没有List
  • Collection<T> allows for members (i.e. Add, Remove, etc.) to be overridden because they are virtual. List<T> does not.
  • 集合 允许成员(例如添加、删除等)被重写,因为它们是虚拟的。< T >不列表。
  • The last part of your question is spot on. A Football team is more than just a list of players, so it should be a class that contains that list of players. Think Composition vs Inheritance. A Football team has a list of players (a roster), it isn't a list of players.
  • 你的问题的最后一部分很到位。一个足球队不仅仅是一个球员列表,所以它应该是一个包含球员列表的类。认为组合和继承。一个足球队有一个球员名单(一个花名册),它不是一个球员名单。

If I were writing this code the class would probably look something like so:

如果我写这段代码,类可能会是这样的:

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.
}

#4


101  

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

Previous code means: a bunch of guys from the street playing football, and they happen to have a name. Something like:

之前的代码是:一群在街上踢足球的家伙,他们碰巧有个名字。喜欢的东西:

为什么不从List继承呢?

Anyway, this code (from m-y's answer)

不管怎样,这个代码(来自m-y的答案)

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; }
    }

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

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

Means: this is a football team which has management, players, admins, etc. Something like:

意思是:这是一支有管理、球员、管理员等的足球队。

为什么不从List继承呢?

This is how is your logic presented in pictures...

这就是你的逻辑如何在图片中呈现……

#5


76  

This is a classic example of composition vs inheritance.

这是一个典型的组合vs继承的例子。

In this specific case:

在这种特定的情况下:

Is the team a list of players with added behavior

团队是否有添加行为的玩家列表?

or

Is the team an object of its own that happens to contain a list of players.

球队本身就是一个对象,碰巧包含了球员的列表。

By extending List you are limiting yourself in a number of ways:

通过扩展列表,你在很多方面限制了自己:

  1. You cannot restrict access (for example, stopping people changing the roster). You get all the List methods whether you need/want them all or not.

    您不能限制访问(例如,阻止人们更改花名册)。无论你是否需要,你都会得到所有的列表方法。

  2. What happens if you want to have lists of other things as well. For example, teams have coaches, managers, fans, equipment, etc. Some of those might well be lists in their own right.

    如果你也想有其他事情的列表会发生什么?例如,球队有教练、经理、球迷、设备等等。其中一些很可能是他们自己列出的。

  3. You limit your options for inheritance. For example you might want to create a generic Team object, and then have BaseballTeam, FootballTeam, etc. that inherit from that. To inherit from List you need to do the inheritance from Team, but that then means that all the various types of team are forced to have the same implementation of that roster.

    您限制了继承的选项。例如,您可能想要创建一个通用的Team对象,然后创建继承自该对象的棒球队、足球队等。要从列表继承您需要从团队继承遗产,但这意味着所有不同类型的团队都必须拥有相同的实现。

Composition - including an object giving the behavior you want inside your object.

组合——包括一个对象,它给出了您想要在对象内部的行为。

Inheritance - your object becomes an instance of the object that has the behavior you want.

继承—您的对象成为具有您想要的行为的对象的实例。

Both have their uses, but this is a clear case where composition is preferable.

两者都有各自的用途,但这是一个明显的选择。

#6


42  

As everyone has pointed out, a team of players is not a list of players. This mistake is made by many people everywhere, perhaps at various levels of expertise. Often the problem is subtle and occasionally very gross, as in this case. Such designs are bad because these violate the Liskov Substitution Principle. The internet has many good articles explaining this concept e.g., http://en.wikipedia.org/wiki/Liskov_substitution_principle

正如每个人都指出的那样,一队球员不是球员的名单。这个错误是由世界各地的许多人犯的,可能是不同专业水平的人。通常情况下,问题很微妙,有时也很严重,就像这种情况。这样的设计是不好的,因为它们违反了Liskov替换原则。互联网上有很多很好的文章来解释这个概念,比如http://en.wikipedia.org/wiki/liskov_replaction_principle

In summary, there are two rules to be preserved in a Parent/Child relationship among classes:

综上所述,在类之间的父/子关系中有两个规则需要保留:

  • a Child should require no characteristic less than what completely defines the Parent.
  • 一个孩子不应该要求比完全定义他的父母更少的特征。
  • a Parent should require no characteristic in addition to what completely defines the Child.
  • 除了完全定义子元素之外,父元素不需要任何其他特性。

In other words, a Parent is a necessary definition of a child, and a child is a sufficient definition of a Parent.

换句话说,父类是子类的必要定义,子类是父类的充分定义。

Here is a way to think through ones solution and apply the above principle that should help one avoid such a mistake. One should test ones hypothesis by verifying if all the operations of a parent class are valid for the derived class both structurally and semantically.

这里有一个方法来思考一个人的解决方案,并应用上面的原则,应该帮助一个人避免这样的错误。通过验证父类的所有操作在结构上和语义上是否对派生类都有效,应该测试一个假设。

  • Is a football team a list of football players? ( Do all properties of a list apply to a team in the same meaning)
    • Is a team a collection of homogenous entities? Yes, team is a collection of Players
    • 一个团队是一个同质实体的集合吗?是的,球队是由球员组成的
    • Is the order of inclusion of players descriptive of the state of the team and does the team ensure that the sequence is preserved unless explicitly changed? No, and No
    • 球员加入的顺序是否描述了球队的状态?如果没有明确的改变,球队是否保证了顺序的保留?没有,没有
    • Are players expected to be included/dropped based on their sequencial position in the team? No
    • 球员是否会因为他们在球队的位置而被包括/被取消?没有
  • 足球队是足球运动员的名单吗?(一个列表的所有属性是否都适用于一个团队)一个团队是否是一个同质实体的集合?是的,团队是一组球员的集合,是对球员加入的顺序的描述,描述了球队的状态,团队是否保证了顺序的保留,除非有明确的改变?没有,也没有球员会因为他们在球队的位置而被包括/被放弃?没有

As you see, only the first characteristic of a list is applicable to a team. Hence a team is not a list. A list would be a implementation detail of how you manage your team, so it should only be used to store the player objects and be manipulated with methods of Team class.

正如您所看到的,列表的第一个特征只适用于一个团队。因此一个团队不是一个列表。列表将是如何管理您的团队的实现细节,因此它应该只用于存储播放器对象,并使用team类的方法进行操作。

At this point I'd like to remark that a Team class should, in my opinion, not even be implemented using a List; it should be implemented using a Set data structure (HashSet, for example) in most cases.

在这一点上,我想指出,在我看来,团队类甚至不应该使用列表来实现;在大多数情况下,应该使用Set数据结构(例如HashSet)实现它。

#7


32  

What if the FootballTeam has a reserves team along with the main team?

如果足球队和主队都有预备队怎么办?

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

How would you model that with?

你要怎么做模型呢?

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

The relationship is clearly has a and not is a.

这种关系显然有a而不是a。

or RetiredPlayers?

还是RetiredPlayers ?

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

As a rule of thumb, if you ever want to inherit from a collection, name the class SomethingCollection.

根据经验,如果您想要从集合继承,请将类命名为SomethingCollection。

Does your SomethingCollection semantically make sense? Only do this if your type is a collection of Something.

你的某物收藏在语义上有意义吗?只有当您的类型是某物的集合时才这样做。

In the case of FootballTeam it doesn't sound right. A Team is more than a Collection. A Team can have coaches, trainers, etc as the other answers have pointed out.

对于足球队来说,这听起来不太对。一个团队不仅仅是一个集合。一个团队可以有教练,教练,等等,正如其他答案所指出的那样。

FootballCollection sounds like a collection of footballs or maybe a collection of football paraphernalia. TeamCollection, a collection of teams.

足球收集听起来像是足球的集合体,也可能是足球用品的收藏。团队集合,团队的集合。

FootballPlayerCollection sounds like a collection of players which would be a valid name for a class that inherits from List<FootballPlayer> if you really wanted to do that.

足球运动员的收藏听起来就像是一群球员的集合,如果你真的想这么做的话,这将是一个继承自列表 <足球运动员> 的职业的有效名称。

Really List<FootballPlayer> is a perfectly good type to deal with. Maybe IList<FootballPlayer> if you are returning it from a method.

真正的名单 <足球运动员> 是一个完美的类型来处理。也许《足球运动员>》,如果你从一个方法返回。

In summary

总之

Ask yourself

问问你自己

  1. Is X a Y? or Has X a Y?

    X是Y吗?或者X a Y?

  2. Do my class names mean what they are?

    我的类名是什么意思?

#8


26  

First of all, it has to do with usability. If you use inheritance, the Team class will expose behavior (methods) that are designed purely for object manipulation. For example, AsReadOnly() or CopyTo(obj) methods make no sense for the team object. Instead of the AddRange(items) method you would probably want a more descriptive AddPlayers(players) method.

首先,它与可用性有关。如果您使用继承,那么Team类将公开纯粹为对象操作而设计的行为(方法)。例如,AsReadOnly()或CopyTo(obj)方法对team对象没有意义。与其使用AddRange(项目)方法,不如使用更具描述性的addplayer(玩家)方法。

If you want to use LINQ, implementing a generic interface such as ICollection<T> or IEnumerable<T> would make more sense.

如果您想使用LINQ,实现一个通用接口,比如ICollection 或IEnumerable 会更有意义。

As mentioned, composition is the right way to go about it. Just implement a list of players as a private variable.

如前所述,合成是正确的方法。只需将参与者列表实现为私有变量。

#9


24  

Design > Implementation

What methods and properties you expose is a design decision. What base class you inherit from is an implementation detail. I feel it's worth taking a step back to the former.

您公开的方法和属性是设计决策。继承的基类是实现细节。我觉得有必要向前者退一步。

An object is a collection of data and behaviour.

对象是数据和行为的集合。

So your first questions should be:

所以你的第一个问题应该是:

  • What data does this object comprise in the model I'm creating?
  • 这个对象在我创建的模型中包含什么数据?
  • What behaviour does this object exhibit in that model?
  • 这个对象在模型中展示了什么行为?
  • How might this change in future?
  • 这种情况在未来会如何改变?

Bear in mind that inheritance implies an "isa" (is a) relationship, whereas composition implies a "has a" (hasa) relationship. Choose the right one for your situation in your view, bearing in mind where things might go as your application evolves.

记住,继承意味着“isa”(是a)关系,而组合意味着“有”(依萨)关系。在您的视图中为您的情况选择一个合适的,考虑到随着应用程序的发展,事情可能会发展到什么程度。

Consider thinking in interfaces before you think in concrete types, as some people find it easier to put their brain in "design mode" that way.

在考虑具体类型之前,先考虑在接口中进行思考,因为有些人发现这样更容易将他们的大脑置于“设计模式”中。

This isn't something everyone does consciously at this level in day to day coding. But if you're mulling this sort of topic, you're treading in design waters. Being aware of it can be liberating.

这不是每个人在日常编码中有意识地做的事情。但是,如果你在考虑这类话题,你就会在设计领域有所作为。意识到它可以是一种解放。

Consider Design Specifics

Take a look at List<T> and IList<T> on MSDN or Visual Studio. See what methods and properties they expose. Do these methods all look like something someone would want to do to a FootballTeam in your view?

看看MSDN或Visual Studio上的列表 和IList 。查看它们公开哪些方法和属性。在你看来,这些方法都像是某人想要对一支足球队做的事情吗?

Does footballTeam.Reverse() make sense to you? Does footballTeam.ConvertAll<TOutput>() look like something you want?

对你来说足球队有意义吗? ()看起来像你想要的吗?

This isn't a trick question; the answer might genuinely be "yes". If you implement/inherit List<Player> or IList<Player>, you're stuck with them; if that's ideal for your model, do it.

这不是一个难题;答案可能是“是”。如果你执行/继承列表 或IList ,你会被他们卡住;如果这对你的模型来说是理想的,那就去做。

If you decide yes, that makes sense, and you want your object to be treatable as a collection/list of players (behaviour), and you therefore want to implement ICollection or IList, by all means do so. Notionally:

如果您决定是,这是有意义的,并且您希望您的对象可以作为一个参与者(行为)的集合/列表来处理,因此您希望实现ICollection或IList,无论如何都要这样做。名义上的:

class FootballTeam : ... ICollection<Player>
{
    ...
}

If you want your object to contain a collection/list of players (data), and you therefore want the collection or list to be a property or member, by all means do so. Notionally:

如果您希望您的对象包含玩家(数据)的集合/列表,因此您希望集合或列表是属性或成员,那么务必这样做。名义上的:

class FootballTeam ...
{
    public ICollection<Player> Players { get { ... } }
}

You might feel that you want people to be able to only enumerate the set of players, rather than count them, add to them or remove them. IEnumerable<Player> is a perfectly valid option to consider.

您可能会觉得,您希望人们能够只列出一组参与者,而不是对他们进行计数、添加或删除。IEnumerable 是完全有效的选择。

You might feel that none of these interfaces are useful in your model at all. This is less likely (IEnumerable<T> is useful in many situations) but it's still possible.

您可能觉得这些接口在您的模型中没有一个是有用的。这是不太可能的(IEnumerable 在很多情况下是有用的),但它仍然是可能的。

Anyone who attempts to tell you that one of these it is categorically and definitively wrong in every case is misguided. Anyone who attempts to tell you it is categorically and definitively right in every case is misguided.

任何试图告诉你其中一个是绝对错误的,在任何情况下都是错误的。任何试图告诉你的人都是绝对正确的,在任何情况下都是被误导的。

Move on to Implementation

Once you've decided on data and behaviour, you can make a decision about implementation. This includes which concrete classes you depend on via inheritance or composition.

一旦您决定了数据和行为,您就可以决定实现。这包括通过继承或组合依赖哪些具体类。

This may not be a big step, and people often conflate design and implementation since it's quite possible to run through it all in your head in a second or two and start typing away.

这可能不是很大的一步,而且人们经常将设计和实现合并在一起,因为很有可能在一两秒钟内在你的头脑中完成所有这些,然后开始输入。

A Thought Experiment

An artificial example: as others have mentioned, a team is not always "just" a collection of players. Do you maintain a collection of match scores for the team? Is the team interchangable with the club, in your model? If so, and if your team isa collection of players, perhaps it also isa collection of staff and/or a collection of scores. Then you end up with:

一个人为的例子:正如其他人所提到的,一个团队并不总是“只是”一个参与者的集合。你是否为球队保留了比赛分数的集合?在你的模型中,团队是否可以与俱乐部交换?如果是这样的话,如果你的球队是球员的集合,那么它也可能是员工的集合或者分数的集合。然后你得到:

class FootballTeam : ... ICollection<Player>, 
                         ICollection<StaffMember>,
                         ICollection<Score>
{
    ....
}

Design notwithstanding, at this point in C# you won't be able to implement all of these by inheriting from List<T> anyway, since C# "only" supports single inheritance. (If you've tried this malarky in C++, you may consider this a Good Thing.) Implementing one collection via inheritance and one via composition is likely to feel dirty. And properties such as Count become confusing to users unless you implement ILIst<Player>.Count and IList<StaffMember>.Count etc. explicitly, and then they're just painful rather than confusing. You can see where this is going; gut feeling whilst thinking down this avenue may well tell you it feels wrong to head in this direction (and rightly or wrongly, your colleagues might also if you implemented it this way!)

尽管进行了设计,在c#中,您仍然无法通过继承List 来实现所有这些,因为c#“only”支持单个继承。(如果您已经在c++中尝试过这种malarky,您可能会认为这是一件好事。)通过继承实现一个集合,通过组合实现一个集合可能会感觉很脏。如果不实现ILIst ,则Count等属性会让用户感到困惑。计数和IList <职员> 。明确地数等等,然后他们只是痛苦而不是困惑。你可以看到它的走向;当你沿着这条路走下去的时候,直觉很可能会告诉你,朝这个方向走是不对的(无论正确与否,你的同事可能也会这么做!)

The Short Answer (Too Late)

The guideline about not inheriting from collection classes isn't C# specific, you'll find it in many programming languages. It is received wisdom not a law. One reason is that in practice composition is considered to often win out over inheritance in terms of comprehensibility, implementability and maintainability. It's more common with real world / domain objects to find useful and consistent "hasa" relationships than useful and consistent "isa" relationships unless you're deep in the abstract, most especially as time passes and the precise data and behaviour of objects in code changes. This shouldn't cause you to always rule out inheriting from collection classes; but it may be suggestive.

不从集合类继承的指导原则不是c#特定的,您可以在许多编程语言中找到它。这是公认的智慧,不是法律。一个原因是在实践中,组合常常被认为在可理解性、可实现性和可维护性方面胜过继承。在现实世界/域对象中,找到有用的、一致的“哈萨”关系比找到有用的、一致的“isa”关系更常见,除非您深入抽象,特别是随着时间的流逝以及代码中对象的精确数据和行为的变化。这不应该导致您总是排除继承集合类;但它可能具有启发性。

#10


21  

A football team is not a list of football players. A football team is composed of a list of football players!

足球队不是足球运动员的名单。一个足球队是由一群足球运动员组成的!

This is logically wrong:

这是逻辑错误:

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

and this is correct:

这是正确的:

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

#11


18  

It depends on the context

When you consider your team as a list of players, you are projecting the "idea" of a foot ball team down to one aspect: You reduce the "team" to the people you see on the field. This projection is only correct in a certain context. In a different context, this might be completely wrong. Imagine you want to become a sponsor of the team. So you have to talk to the managers of the team. In this context the team is projected to the list of its managers. And these two lists usually don't overlap very much. Other contexts are the current versus the former players, etc.

当你把你的球队看成是一个球员的列表时,你是在把足球队的“想法”投射到一个方面:你把“团队”缩小到你在场上看到的人身上。这种预测只有在一定的背景下才正确。在另一种情况下,这可能是完全错误的。假设你想成为团队的赞助商。所以你得和团队的经理谈谈。在这种情况下,团队被投射到经理的列表中。这两个列表通常没有重叠。其他的上下文是当前和以前的玩家,等等。

Unclear semantics

So the problem with considering a team as a list of its players is that its semantic depends on the context and that it cannot be extended when the context changes. Additionally it is hard to express, which context you are using.

因此,将一个团队视为其参与者列表的问题在于,它的语义依赖于上下文,并且当上下文发生变化时,它不能被扩展。此外,它很难表达,您正在使用的上下文。

Classes are extensible

When you using a class with only one member (e.g. IList activePlayers), you can use the name of the member (and additionally its comment) to make the context clear. When there are additional contexts, you just add an additional member.

当您使用只有一个成员(例如IList activeplayer)的类时,您可以使用成员的名称(以及它的注释)来明确上下文。当有其他上下文时,您只需添加一个额外的成员。

Classes are more complex

In some cases it might be overkill to create a extra class. Each class definition must be loaded through the classloader and will be cached by the virtual machine. This costs you runtime performance and memory. When you have a very specific context it might be OK to consider a football team as a list of players. But in this case, you should really just use a IList , not a class derived from it.

在某些情况下,创建一个额外的类可能有些过分。每个类定义都必须通过类加载器加载,并由虚拟机缓存。这将消耗运行时性能和内存。当你有一个非常具体的背景时,考虑一个足球队作为球员名单是可以的。但是在这种情况下,你应该使用IList,而不是从它派生的类。

Conclusion / Considerations

When you have a very specific context, it is OK to consider a team as a list of players. For example inside a method it is completely OK to write

当你有一个非常特定的背景时,可以将一个团队看作是一个球员列表。例如,在方法内部,完全可以编写

IList<Player> footballTeam = ...

When using F#, it can even be OK to create a type abbreviation

在使用f#时,甚至可以创建一个类型缩写。

type FootballTeam = IList<Player>

But when the context is broader or even unclear, you should not do this. This is especially the case, when you create a new class, where it is not clear in which context it may be used in the future. A warning sign is when you start to add additional attributes to your class (name of the team, coach, etc.). This is a clear sign that the context where the class will be used is not fixed and will change in the future. In this case you cannot consider the team as a list of players, but you should model the list of the (currently active, not injured, etc.) players as an attribute of the team.

但当背景更广泛或甚至不清楚时,你不应该这么做。当您创建一个新类时,尤其如此,在这种情况下,不清楚将来在什么上下文中可以使用它。警告标志是当您开始向您的类添加附加属性时(团队名称、教练等)。这是一个明确的信号,表明将使用类的上下文不是固定的,将来会发生变化。在这种情况下,您不能将团队视为一个球员列表,但是您应该将(当前活动的,而不是受伤的,等等)球员列表作为团队的属性进行建模。

#12


18  

Let me rewrite your question. so you might see the subject from a different perspective.

让我重写一下你的问题。所以你可能会从不同的角度看待这个问题。

When I need to represent a football team, I understand that it is basically a name. Like: "The Eagles"

当我需要代表一个足球队时,我知道它基本上就是一个名字。如:“老鹰”

string team = new string();

Then later I realized teams also have players.

后来我意识到球队也有球员。

Why can't I just extend the string type so that it also holds a list of players?

为什么我不能扩展字符串类型,让它也包含玩家列表?

Your point of entry into the problem is arbitrary. Try to think what does a team have (properties), not what it is.

你对这个问题的切入点是任意的。试着思考一个团队有什么(属性),而不是它是什么。

After you do that, you could see if it shares properties with other classes. And think about inheritance.

在您这样做之后,您可以看到它是否与其他类共享属性。想想继承。

#13


13  

Does allowing people to say

允许人们说吗

myTeam.subList(3, 5);

make any sense at all? If not then it shouldn't be a List.

有什么意义吗?如果不是,那就不应该是列表。

#14


13  

Just because I think the other answers pretty much go off on a tangent of whether a football team "is-a" List<FootballPlayer> or "has-a" List<FootballPlayer>, which really doesn't answer this question as written.

仅仅因为我认为其他的答案都与一个足球队的“a”或“hasa”有关,这并不能回答这个问题。

The OP chiefly asks for clarification on guidelines for inheriting from List<T>:

OP主要要求明确从List 继承指南:

A guideline says that you shouldn't inherit from List<T>. Why not?

一个指导原则说你不应该继承List 。为什么不呢?

Because List<T> has no virtual methods. This is less of a problem in your own code, since you can usually switch out the implementation with relatively little pain - but can be a much bigger deal in a public API.

因为List 没有虚方法。在您自己的代码中,这并不是什么问题,因为您通常可以轻松地切换实现——但是在公共API中,这可能是一件大事。

What is a public API and why should I care?

什么是公共API,我为什么要关心?

A public API is an interface you expose to 3rd party programmers. Think framework code. And recall that the guidelines being referenced are the ".NET Framework Design Guidelines" and not the ".NET Application Design Guidelines". There is a difference, and - generally speaking - public API design is a lot more strict.

公共API是向第三方程序员公开的接口。认为框架代码。记住,参考的指导方针是"netframework设计指南“而不是”。网络应用程序设计指南”。这是有区别的,一般来说,公共API设计要严格得多。

If my current project does not and is not likely to ever have this public API, can I safely ignore this guideline? If I do inherit from List and it turns out I need a public API, what difficulties will I have?

如果我当前的项目没有并且不太可能有这个公共API,我可以安全地忽略这个指导方针吗?如果我继承了List并且需要一个公共API,会有什么困难?

Pretty much, yeah. You may want to consider the rationale behind it to see if it applies to your situation anyway, but if you're not building a public API then you don't particularly need to worry about API concerns like versioning (of which, this is a subset).

差不多,是的。您可能需要考虑它背后的基本原理,看看它是否适用于您的情况,但是如果您不构建一个公共API,那么您就不需要特别担心像版本控制这样的API问题(这是一个子集)。

If you add a public API in the future, you will either need to abstract out your API from your implementation (by not exposing your List<T> directly) or violate the guidelines with the possible future pain that entails.

如果您在将来添加一个公共API,您将需要从实现中抽象出您的API(通过不直接公开您的列表 ),或者违反指导方针,可能会带来未来的痛苦。

Why does it even matter? A list is a list. What could possibly change? What could I possibly want to change?

这有什么关系呢?列表就是一个列表。什么能改变吗?我能改变什么呢?

Depends on the context, but since we're using FootballTeam as an example - imagine that you can't add a FootballPlayer if it would cause the team to go over the salary cap. A possible way of adding that would be something like:

这取决于背景,但既然我们是用足球队作为例子,假设你不能增加一个足球运动员,如果它会导致球队越过工资帽。可能的增加方式是:

 class FootballTeam : List<FootballPlayer> {
     override void Add(FootballPlayer player) {
        if (this.Sum(p => p.Salary) + player.Salary > SALARY_CAP)) {
          throw new InvalidOperationException("Would exceed salary cap!");
        }
     }
 }

Ah...but you can't override Add because it's not virtual (for performance reasons).

啊…但是您不能重写Add,因为它不是虚拟的(出于性能原因)。

If you're in an application (which, basically, means that you and all of your callers are compiled together) then you can now change to using IList<T> and fix up any compile errors:

如果您在一个应用程序中(基本上,这意味着您和所有调用者都被编译在一起),那么您现在可以更改为使用IList 并修复任何编译错误:

 class FootballTeam : IList<FootballPlayer> {
     private List<FootballPlayer> Players { get; set; }

     override void Add(FootballPlayer player) {
        if (this.Players.Sum(p => p.Salary) + player.Salary > SALARY_CAP)) {
          throw new InvalidOperationException("Would exceed salary cap!");
        }
     }
     /* boiler plate for rest of IList */
 }

but, if you've publically exposed to a 3rd party you just made a breaking change that will cause compile and/or runtime errors.

但是,如果您公开暴露给第三方,您刚刚做了一个破坏性的更改,将导致编译和/或运行时错误。

TL;DR - the guidelines are for public APIs. For private APIs, do what you want.

TL;DR -指南是针对公共api的。对于私有api,您想做什么就做什么。

#15


11  

It depends on the behaviour of your "team" object. If it behaves just like a collection, it might be OK to represent it first with a plain List. Then you might start to notice that you keep duplicating code that iterates on the list; at this point you have the option of creating a FootballTeam object that wraps the list of players. The FootballTeam class becomes the home for all the code that iterates on the list of players.

这取决于“团队”对象的行为。如果它的行为就像一个集合,那么可以先用一个普通的列表来表示它。然后您可能会注意到,您保留了迭代列表上的重复代码;此时,您可以选择创建一个足球团队对象来包装球员列表。足球队成为了球员名单上所有代码的家。

It makes my code needlessly verbose. I must now call my_team.Players.Count instead of just my_team.Count. Thankfully, with C# I can define indexers to make indexing transparent, and forward all the methods of the internal List... But that's a lot of code! What do I get for all that work?

它使我的代码不必要地冗长。我现在必须呼叫my_team.Players。计数,而不是my_team.Count。值得庆幸的是,使用c#我可以定义索引器,使索引变得透明,并转发内部列表的所有方法……但那是很多的代码!做了那么多工作,我能得到什么?

Encapsulation. Your clients need not know what goes on inside of FootballTeam. For all your clients know, it might be implemented by looking the list of players up in a database. They don't need to know, and this improves your design.

封装。你的客户不需要知道球队内部发生了什么。对于所有您的客户来说,它可以通过在数据库中查找玩家列表来实现。他们不需要知道,这改进了你的设计。

It just plain doesn't make any sense. A football team doesn't "have" a list of players. It is the list of players. You don't say "John McFootballer has joined SomeTeam's players". You say "John has joined SomeTeam". You don't add a letter to "a string's characters", you add a letter to a string. You don't add a book to a library's books, you add a book to a library.

这是毫无意义的。一个足球队没有球员名单。这是球员名单。你不会说“约翰·麦克球员加入了某个队的球员”。你说“约翰加入了一个团队”。你不向“字符串的字符”添加一个字母,而是向字符串添加一个字母。你不是把一本书加到图书馆的书里,而是把一本书加到图书馆里。

Exactly :) you will say footballTeam.Add(john), not footballTeam.List.Add(john). The internal list will not be visible.

确切地说:)你会说成足球队。add(约翰),而不是football . list . add(约翰)。内部列表将不可见。

#16


10  

What is the correct C# way of representing a data structure...

c#表示数据结构的正确方式是什么?

Remeber, "All models are wrong, but some are useful." -George E. P. Box

记住,“所有的模型都是错误的,但有些是有用的。”——e . p .盒子

There is no a "correct way", only a useful one.

没有“正确的方法”,只有有用的方法。

Choose one that is useful to you and/your users. That's it. Develop economically, don't over-engineer. The less code you write, the less code you will need to debug. (read the following editions).

选择一个对你和你的用户有用的。就是这样。发展经济,不过度工程化。编写的代码越少,需要调试的代码就越少。(阅读下面的版本)。

-- Edited

——编辑

My best answer would be... it depends. Inheriting from a List would expose the clients of this class to methods that may be should not be exposed, primarily because FootballTeam looks like a business entity.

我最好的答案是……视情况而定。从列表中继承将向该类的客户端公开不应该公开的方法,主要是因为足球队看起来像一个商业实体。

-- Edition 2

——版2

I sincerely don't remember to what I was referring on the “don't over-engineer” comment. While I believe the KISS mindset is a good guide, I want to emphasize that inheriting a business class from List would create more problems than it resolves, due abstraction leakage.

我真的不记得我指的是什么“不要过度设计”的评论。尽管我相信KISS思维模式是一个很好的指南,但我想强调的是,从List继承一个业务类会产生比它所解决的更多的问题,由于抽象泄漏。

On the other hand, I believe there are a limited number of cases where simply to inherit from List is useful. As I wrote in the previous edition, it depends. The answer to each case is heavily influenced by both knowledge, experience and personal preferences.

另一方面,我认为,仅从列表中继承的案例数量有限。正如我在上一版中所写的,这要看情况而定。每个案例的答案都受到知识、经验和个人偏好的严重影响。

Thanks to @kai for helping me to think more precisely about the answer.

感谢@kai帮助我更准确地思考答案。

#17


9  

There are a lot excellent answers here, but I want to touch on something I didn't see mentioned: Object oriented design is about empowering objects.

这里有很多优秀的答案,但我想谈谈我没有提到的东西:面向对象的设计是关于增强对象的。

You want to encapsulate all your rules, additional work and internal details inside an appropriate object. In this way other objects interacting with this one don't have to worry about it all. In fact, you want to go a step further and actively prevent other objects from bypassing these internals.

您希望在适当的对象中封装所有规则、附加工作和内部细节。通过这种方式,与这个对象交互的其他对象不必担心所有这些。实际上,您希望更进一步,积极地防止其他对象绕过这些内部元素。

When you inherit from List, all other objects can see you as a List. They have direct access to the methods for adding and removing players. And you'll have lost your control; for example:

当您从列表继承时,所有其他对象都可以将您视为一个列表。他们可以直接访问添加和删除播放器的方法。你会失去控制;例如:

Suppose you want to differentiate when a player leaves by knowing whether they retired, resigned or were fired. You could implement a RemovePlayer method that takes an appropriate input enum. However, by inheriting from List, you would be unable to prevent direct access to Remove, RemoveAll and even Clear. As a result, you've actually disempowered your FootballTeam class.

假设你想通过知道球员是退役、辞职还是被解雇来区分他们何时离开。您可以实现RemovePlayer方法,该方法接受适当的输入枚举。但是,通过继承List,您将无法阻止直接访问来删除、RemoveAll甚至清除。结果,你实际上剥夺了你的足球队的权利。


Additional thoughts on encapsulation... You raised the following concern:

额外的思想封装…你提出以下关切:

It makes my code needlessly verbose. I must now call my_team.Players.Count instead of just my_team.Count.

它使我的代码不必要地冗长。我现在必须呼叫my_team.Players。用Count代替my_team.Count。

You're correct, that would be needlessly verbose for all clients to use you team. However, that problem is very small in comparison to the fact that you've exposed List Players to all and sundry so they can fiddle with your team without your consent.

您是对的,这对于所有使用您团队的客户来说是不必要的冗长。然而,这个问题与你让名单球员接触到所有人的事实相比是非常小的,这样他们就可以在没有你同意的情况下和你的团队一起玩。

You go on to say:

你接着说:

It just plain doesn't make any sense. A football team doesn't "have" a list of players. It is the list of players. You don't say "John McFootballer has joined SomeTeam's players". You say "John has joined SomeTeam".

这是毫无意义的。一个足球队没有球员名单。这是球员名单。你不会说“约翰·麦克球员加入了某个队的球员”。你说“约翰加入了一个团队”。

You're wrong about the first bit: Drop the word 'list', and it's actually obvious that a team does have players.
However, you hit the nail on the head with the second. You don't want clients calling ateam.Players.Add(...). You do want them calling ateam.AddPlayer(...). And your implemention would (possibly amongst other things) call Players.Add(...) internally.

你在第一点上错了:去掉“列表”这个词,很明显,一个球队确实有球员。然而,你说的对极了。您不希望客户调用ateam.Players.Add(…)。你确实希望他们调用ateam.AddPlayer(…)。你的实现会(可能还有其他东西)调用玩家。


Hopefully you can see how important encapsulation is to the objective of empowering your objects. You want to allow each class to do its job well without fear of interference from other objects.

希望您能够看到封装对于实现授权对象的目标是多么重要。您希望允许每个类出色地完成其工作,而不必担心来自其他对象的干扰。

#18


8  

This reminds me of the "Is a" versus "has a" tradeoff. Sometimes it is easier and makesmore sense to inherit directly from a super class. Other times it makes more sense to create a standalone class and include the class you would have inherited from as a member variable. You can still access the functionality of the class but are not bound to the interface or any other constraints that might come from inheriting from the class.

这让我想起了“是”与“是”之间的权衡。有时直接从超类继承更容易,也更有意义。其他时候,创建一个独立的类更有意义,并且包含从成员变量继承的类。您仍然可以访问类的功能,但不绑定到接口或从类继承而来的任何其他约束。

Which do you do? As with a lot of things...it depends on the context. The guide I would use is that in order to inherit from another class there truly should be an "is a" relationship. So if you a writing a class called BMW, it could inherit from Car because a BMW truly is a car. A Horse class can inherit from the Mammal class because a horse actually is a mammal in real life and any Mammal functionality should be relevant to Horse. But can you say that a team is a list? From what I can tell, it does not seem like a Team really "is a" List. So in this case, I would have a List as a member variable.

,你会怎么做?和很多事情一样……这取决于上下文。我使用的指南是,为了从另一个类继承,应该有一个“is”关系。所以如果你写一个叫做宝马的课程,它可以从汽车继承,因为宝马确实是一辆车。马类可以从哺乳动物类继承,因为马在现实生活中实际上是哺乳动物,任何哺乳动物的功能都应该与马相关。但是你能说一个团队是一个列表吗?据我所知,这并不是一个真正的“团队”。在这种情况下,我将一个列表作为成员变量。

#19


6  

My dirty secret: I don't care what people say, and I do it. .NET Framework is spread with "XxxxCollection" (UIElementCollection for top of my head example).

我的肮脏秘密:我不在乎别人怎么说,我就是这么做的。net框架是用“xxxxxxcollection”(我的head示例的顶部是UIElementCollection)来扩展的。

So what stops me saying:

是什么阻止我说:

team.Players.ByName("Nicolas")

When I find it better than

当我发现它比

team.ByName("Nicolas")

Moreover, my PlayerCollection might be used by other class, like "Club" without any code duplication.

此外,我的PlayerCollection可能被其他类使用,比如“Club”,没有任何代码复制。

club.Players.ByName("Nicolas")

Best practices of yesterday, might not be the one of tomorrow. There is no reason behind most best practices, most are only wide agreement among the community. Instead of asking the community if it will blame you when you do that ask yourself, what is more readable and maintainable?

昨天的最佳实践,可能不是明天的。大多数最佳实践的背后没有任何理由,大多数都只是社区之间的广泛共识。当你这样做的时候,不要问社区是否会责备你,而要问自己,什么东西更容易阅读和维护?

team.Players.ByName("Nicolas") 

or

team.ByName("Nicolas")

Really. Do you have any doubt? Now maybe you need to play with other technical constraints that prevent you to use List<T> in your real use case. But don't add a constraint that should not exist. If Microsoft did not document the why, then it is surely a "best practice" coming from nowhere.

真的。你有什么疑问吗?现在,您可能需要处理其他技术约束,以防止在实际用例中使用List 。但是不要添加不应该存在的约束。如果微软没有记录下原因,那么毫无疑问这是一种“最佳实践”。

#20


6  

What the guidelines say is that the public API should not reveal the internal design decision of whether you are using a list, a set, a dictionary, a tree or whatever. A "team" is not necessarily a list. You may implement it as a list but users of your public API should use you class on a need to know basis. This allows you to change your decision and use a different data structure without affecting the public interface.

指导方针说的是,公共API不应该显示您是否在使用列表、集合、字典、树或其他东西的内部设计决策。一个“团队”不一定是一个列表。您可以将它作为一个列表来实现,但是您的公共API的用户应该根据需要使用您的类。这允许您更改决策并使用不同的数据结构,而不会影响公共接口。

#21


4  

When they say List<T> is "optimized" I think they want to mean that it doesn't have features like virtual methods which are bit more expensive. So the problem is that once you expose List<T> in your public API, you loose ability to enforce business rules or customize its functionality later. But if you are using this inherited class as internal within your project (as opposed to potentially exposed to thousands of your customers/partners/other teams as API) then it may be OK if it saves your time and it is the functionality you want to duplicate. The advantage of inheriting from List<T> is that you eliminate lot of dumb wrapper code that is just never going to be customized in foreseeable future. Also if you want your class to explicitly have exact same semantics as List<T> for the life of your APIs then also it may be OK.

当他们说List 是“优化”的时候,我认为他们想要的是它没有像虚拟方法那样的功能,而这些方法更贵一些。因此,问题是一旦在您的公共API中公开了List ,您就会失去执行业务规则的能力,或者在以后定制它的功能。但是,如果您在项目内部使用这个继承的类(而不是将它作为API公开给成千上万的客户/合作伙伴/其他团队),那么如果它节省了您的时间,并且是您想要复制的功能,那么它可能是可以的。继承List 的好处是,您可以消除许多哑包装器代码,这些代码在可预见的将来永远不会被定制。另外,如果您希望您的类在api的生命周期中具有与List 完全相同的语义,那么它也可以。

I often see lot of people doing tons of extra work just because of FxCop rule says so or someone's blog says it's a "bad" practice. Many times, this turns code in to design pattern palooza weirdness. As with lot of guideline, treat it as guideline that can have exceptions.

我经常看到很多人因为FxCop规则而做了大量的额外工作,或者有人的博客说这是一种“坏”行为。很多时候,这就把代码变成了设计模式的怪异。和许多指导方针一样,把它当作有例外的指导方针。

#22


3  

If your class users need all the methods and properties** List has, you should derive your class from it. If they don't need them, enclose the List and make wrappers for methods your class users actually need.

如果您的类用户需要所有方法和属性**列表,您应该从它派生类。如果它们不需要它们,请将列表括起来,并为类用户实际需要的方法创建包装器。

This is a strict rule, if you write a public API, or any other code that will be used by many people. You may ignore this rule if you have a tiny app and no more than 2 developers. This will save you some time.

这是一个严格的规则,如果您编写一个公共API,或者编写任何其他将被许多人使用的代码。如果你有一个小的应用程序,并且不超过2个开发人员,你可以忽略这个规则。这会节省你一些时间。

For tiny apps, you may also consider choosing another, less strict language. Ruby, JavaScript - anything that allows you to write less code.

对于小型应用程序,您还可以考虑选择另一种不那么严格的语言。Ruby, JavaScript——任何允许你少写代码的东西。

#23


3  

I just wanted to add that Bertrand Meyer, the inventor of Eiffel and design by contract, would have Team inherit from List<Player> without so much as batting an eyelid.

我只是想补充一点,埃菲尔的发明者和合同设计,贝特朗·迈耶会让一队从List 不费吹灰之力就继承下来。

In his book, Object-Oriented Software Construction, he discusses the implementation of a GUI system where rectangular windows can have child windows. He simply has Window inherit from both Rectangle and Tree<Window> to reuse the implementation.

在他的《面向对象的软件构建》一书中,他讨论了GUI系统的实现,矩形窗口可以有子窗口。他只需要从矩形和树 <窗口> 继承窗口来重用实现。

However, C# is not Eiffel. The latter supports multiple inheritance and renaming of features. In C#, when you subclass, you inherit both the interface and the implemenation. You can override the implementation, but the calling conventions are copied directly from the superclass. In Eiffel, however, you can modify the names of the public methods, so you can rename Add and Remove to Hire and Fire in your Team. If an instance of Team is upcast back to List<Player>, the caller will use Add and Remove to modify it, but your virtual methods Hire and Fire will be called.

然而,c#并不是埃菲尔。后者支持多重继承和特性重命名。在c#中,当您子类化时,您继承了接口和实现。您可以重写实现,但是调用约定直接从超类复制。然而,在Eiffel中,您可以修改公共方法的名称,因此您可以重命名Add and Remove来雇佣和解雇您的团队。如果一个Team的实例被上传至列表 ,调用者将使用Add and Remove来修改它,但是您的虚拟方法Hire and Fire将被调用。

#24


2  

While I don't have a complex comparison as most of these answers do, I would like to share my method for handling this situation. By extending IEnumerable<T>, you can allow your Team class to support Linq query extensions, without publicly exposing all the methods and properties of List<T>.

虽然我不像大多数答案那样有一个复杂的比较,但我愿意分享我处理这种情况的方法。通过扩展IEnumerable ,您可以允许您的Team类支持Linq查询扩展,而不公开显示List 的所有方法和属性。

class Team : IEnumerable<Player>
{
    private readonly List<Player> playerList;

    public Team()
    {
        playerList = new List<Player>();
    }

    public Enumerator GetEnumerator()
    {
        return playerList.GetEnumerator();
    }

    ...
}

class Player
{
    ...
}

#25


0  

I think I don't agree with your generalization. A team isn't just a collection of players. A team has so much more information about it - name, emblem, collection of management/admin staff, collection of coaching crew, then collection of players. So properly, your FootballTeam class should have 3 collections and not itself be a collection; if it is to properly model the real world.

我想我不同意你的概括。一个团队不只是一群球员。一个团队有很多关于它的信息-名字,徽章,管理/管理人员的集合,教练组的集合,然后是球员的集合。所以正确地说,你的足球队应该有3个集合,而不是一个集合;如果它是正确地模拟现实世界。

You could consider a PlayerCollection class which like the Specialized StringCollection offers some other facilities - like validation and checks before objects are added to or removed from the internal store.

您可以考虑PlayerCollection类,它类似于专门的StringCollection提供了一些其他功能——比如在对象添加或从内部存储中删除之前进行验证和检查。

Perhaps, the notion of a PlayerCollection betters suits your preferred approach?

也许,玩家集合的概念适合你的偏好?

public class PlayerCollection : Collection<Player> 
{ 
}

And then the FootballTeam can look like this:

然后这个足球队就像这样:

public class FootballTeam 
{ 
    public string Name { get; set; }
    public string Location { get; set; }

    public ManagementCollection Management { get; protected set; } = new ManagementCollection();

    public CoachingCollection CoachingCrew { get; protected set; } = new CoachingCollection();

    public PlayerCollection Players { get; protected set; } = new PlayerCollection();
}

#1


1107  

There are some good answers here. I would add to them the following points.

这里有一些很好的答案。我想补充以下几点。

What is the correct C# way of representing a data structure, which, "logically" (that is to say, "to the human mind") is just a list of things with a few bells and whistles?

c#表示数据结构的正确方式是什么,它“逻辑地”(也就是说,“对人类的思想”)仅仅是一个有一些铃铛和哨声的东西的列表?

Ask any ten non-computer-programmer people who are familiar with the existence of football to fill in the blank:

让任何十个熟悉足球存在的非电脑程序员来填补这个空白:

A football team is a particular kind of _____

Did anyone say "list of football players with a few bells and whistles", or did they all say "sports team" or "club" or "organization"? Your notion that a football team is a particular kind of list of players is in your human mind and your human mind alone.

有没有人说过“足球运动员名单上有一些花哨的东西”,或者他们都说“运动队”、“俱乐部”或“组织”?你认为足球队是一种特殊的球员名单,这一观点只存在于你的人类思维中,也存在于你的人类思维中。

List<T> is a mechanism. Football team is a business object -- that is, an object that represents some concept that is in the business domain of the program. Don't mix those! A football team is a kind of team; it has a roster, a roster is a list of players. A roster is not a particular kind of list of players. A roster is a list of players. So make a property called Roster that is a List<Player>. And make it ReadOnlyList<Player> while you're at it, unless you believe that everyone who knows about a football team gets to delete players from the roster.

列出< T >是一种机制。足球队是一个业务对象——即表示程序的业务领域中的某个概念的对象。不要把这些!足球队是一种球队;它有一个花名册,花名册是球员名单。花名册并不是一种特定的球员名单。花名册是一份球员名单。因此,创建一个名为列表的属性,它是一个列表 。如果你不相信每个知道一个足球队的人都可以把球员从名单中删除,那就把它重新列出来。

Is inheriting from List<T> always unacceptable?

继承List 总是不可接受的吗?

Unacceptable to who? Me? No.

无法接受谁?我吗?不。

When is it acceptable?

什么时候可以接受吗?

When you're building a mechanism that extends the List<T> mechanism.

当您构建扩展列表 机制的机制时。

What must a programmer consider, when deciding whether to inherit from List<T> or not?

当决定是否从List 继承时,程序员必须考虑什么?

Am I building a mechanism or a business object?

我构建的是机制还是业务对象?

But that's a lot of code! What do I get for all that work?

但那是很多的代码!做了那么多工作,我能得到什么?

You spent more time typing up your question that it would have taken you to write forwarding methods for the relevant members of List<T> fifty times over. You're clearly not afraid of verbosity, and we are talking about a very small amount of code here; this is a few minutes work.

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

UPDATE

I gave it some more thought and there is another reason to not model a football team as a list of players. In fact it might be a bad idea to model a football team as having a list of players too. The problem with a team as/having a list of players is that what you've got is a snapshot of the team at a moment in time. I don't know what your business case is for this class, but if I had a class that represented a football team I would want to ask it questions like "how many Seahawks players missed games due to injury between 2003 and 2013?" or "What Denver player who previously played for another team had the largest year-over-year increase in yards ran?" or "Did the Piggers go all the way this year?"

我给了它更多的思考,还有另外一个理由不把一个足球队当作球员名单。事实上,把一个足球队打造成一个球员名单也不是个好主意。球队的问题在于你所拥有的只是球队在某一时刻的快照。我不知道你的业务案例是对于这门课,但是如果我有一个类代表一个足球队我想问这样的问题“多少海鹰队球员因伤错过了游戏在2003年和2013年之间?”和“什么丹佛球员以前打了另一个团队的最大码了同比增长?”或“今年Piggers一路吗?”

That is, a football team seems to me to be well modeled as a collection of historical facts such as when a player was recruited, injured, retired, etc. Obviously the current player roster is an important fact that should probably be front-and-center, but there may be other interesting things you want to do with this object that require a more historical perspective.

即足球队我看来,建模为历史事实的集合,如当一个球员招募了,受伤了,退休了,等。显然目前的球员名单应该放在一个重要的事实,但可能还有其他有趣的事情你想做的这个对象需要更多的历史观点。

#2


227  

Lastly, some suggest wrapping the List in something:

最后,一些人建议将清单包装在一些东西上:

That is the correct way. "Needlessly wordy" is a bad way to look at this. It has an explicit meaning when you write my_team.Players.Count. You want to count the players.

这是正确的方法。“不必要地啰嗦”是一个不好的看法。当您编写my_team.Players.Count时,它具有明确的含义。你要数球员的数目。

my_team.Count

..means nothing. Count what?

. .没有任何意义。计算什么?

A team isn't a list - the consist of more than just a list of players. A team owns players, so players should be part of it (a member).

一个团队不是一个列表——它不仅仅是一个球员列表。球队拥有球员,所以球员应该是球队的一员。

If you're really worried about it being overly verbose, you can always expose properties from the team:

如果您真的担心它过于冗长,您可以始终公开来自团队的属性:

public int PlayerCount {
    get {
        return Players.Count;
    }
}

..which becomes:

. .这就变成:

my_team.PlayerCount

This has meaning now and adheres to The Law Of Demeter.

这现在有了意义,并且遵循了德墨忒耳定律。

You should also consider adhering to the Composite reuse principle. By inheriting from List<T>, you're saying a team is a list of players and exposing needless methods out of it. This is incorrect - as you stated, a team is more than a list of players: it has a name, managers, board members, trainers, medical staff, salary caps, etc. By having your team class contain a list of players, you're saying "A team has a list of players", but it can also have other things.

您还应该考虑遵守复合重用原则。通过继承List ,您是说一个团队是一个球员的列表,并从其中暴露出不必要的方法。这是不正确的,正如你所说,一个团队不仅仅是玩家的列表:它有一个名字,经理、董事会成员,运动鞋,医务人员、工资帽,等。通过你的团队类包含一个玩家列表,你说“一个团队的球员”,但它也可以有其他的事情。

#3


107  

Wow, your post has an entire slew of questions and points. Most of the reasoning you get from Microsoft is exactly on point. Let's start with everything about List<T>

哇,你的帖子里有很多问题和观点。你从微软得到的大部分推理都是正确的。让我们从List 开始。

  • List<T> is highly optimized. It's main usage is to be used as a private member of an object.
  • < T >是高度优化的列表。它的主要用法是作为对象的私有成员使用。
  • Microsoft did not seal it because sometimes you might want to create a class that has a friendlier name: class MyList<T, TX> : List<CustomObject<T, Something<TX>> { ... }. Now it's as easy as doing var list = new MyList<int, string>();.
  • 微软没有封印它,因为有时您可能想要创建一个更友好的类:class MyList : List >{…}。现在它就像使用var列表= new MyList ()一样简单; ,> ,>
  • CA1002: Do not expose generic lists: Basically, even if you plan to use this app as the sole developer, it's worthwhile to develop with good coding practices, so they become instilled into you and second nature. You are still allowed to expose the list as an IList<T> if you need any consumer to have an indexed list. This let's you change the implementation within a class later on.
  • CA1002:不要公开通用列表:基本上,即使您打算使用这个应用作为唯一的开发人员,使用良好的编码实践进行开发也是值得的,因此它们会逐渐灌输给您和您的第二天性。您仍然可以将列表作为IList 公开,如果您需要任何消费者有一个索引列表。让我们稍后在类中更改实现。
  • Microsoft made Collection<T> very generic because it is a generic concept... the name says it all; it is just a collection. There are more precise versions such as SortedCollection<T>, ObservableCollection<T>, ReadOnlyCollection<T>, etc. each of which implement IList<T> but not List<T>.
  • 微软使Collection 非常通用,因为它是一个通用的概念……名字就说明了一切;它只是一个集合。有更精确的版本,如SortedCollection , ObservableCollection , ReadOnlyCollection 等,每一个实现IList ,但没有List
  • Collection<T> allows for members (i.e. Add, Remove, etc.) to be overridden because they are virtual. List<T> does not.
  • 集合 允许成员(例如添加、删除等)被重写,因为它们是虚拟的。< T >不列表。
  • The last part of your question is spot on. A Football team is more than just a list of players, so it should be a class that contains that list of players. Think Composition vs Inheritance. A Football team has a list of players (a roster), it isn't a list of players.
  • 你的问题的最后一部分很到位。一个足球队不仅仅是一个球员列表,所以它应该是一个包含球员列表的类。认为组合和继承。一个足球队有一个球员名单(一个花名册),它不是一个球员名单。

If I were writing this code the class would probably look something like so:

如果我写这段代码,类可能会是这样的:

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.
}

#4


101  

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

Previous code means: a bunch of guys from the street playing football, and they happen to have a name. Something like:

之前的代码是:一群在街上踢足球的家伙,他们碰巧有个名字。喜欢的东西:

为什么不从List继承呢?

Anyway, this code (from m-y's answer)

不管怎样,这个代码(来自m-y的答案)

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; }
    }

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

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

Means: this is a football team which has management, players, admins, etc. Something like:

意思是:这是一支有管理、球员、管理员等的足球队。

为什么不从List继承呢?

This is how is your logic presented in pictures...

这就是你的逻辑如何在图片中呈现……

#5


76  

This is a classic example of composition vs inheritance.

这是一个典型的组合vs继承的例子。

In this specific case:

在这种特定的情况下:

Is the team a list of players with added behavior

团队是否有添加行为的玩家列表?

or

Is the team an object of its own that happens to contain a list of players.

球队本身就是一个对象,碰巧包含了球员的列表。

By extending List you are limiting yourself in a number of ways:

通过扩展列表,你在很多方面限制了自己:

  1. You cannot restrict access (for example, stopping people changing the roster). You get all the List methods whether you need/want them all or not.

    您不能限制访问(例如,阻止人们更改花名册)。无论你是否需要,你都会得到所有的列表方法。

  2. What happens if you want to have lists of other things as well. For example, teams have coaches, managers, fans, equipment, etc. Some of those might well be lists in their own right.

    如果你也想有其他事情的列表会发生什么?例如,球队有教练、经理、球迷、设备等等。其中一些很可能是他们自己列出的。

  3. You limit your options for inheritance. For example you might want to create a generic Team object, and then have BaseballTeam, FootballTeam, etc. that inherit from that. To inherit from List you need to do the inheritance from Team, but that then means that all the various types of team are forced to have the same implementation of that roster.

    您限制了继承的选项。例如,您可能想要创建一个通用的Team对象,然后创建继承自该对象的棒球队、足球队等。要从列表继承您需要从团队继承遗产,但这意味着所有不同类型的团队都必须拥有相同的实现。

Composition - including an object giving the behavior you want inside your object.

组合——包括一个对象,它给出了您想要在对象内部的行为。

Inheritance - your object becomes an instance of the object that has the behavior you want.

继承—您的对象成为具有您想要的行为的对象的实例。

Both have their uses, but this is a clear case where composition is preferable.

两者都有各自的用途,但这是一个明显的选择。

#6


42  

As everyone has pointed out, a team of players is not a list of players. This mistake is made by many people everywhere, perhaps at various levels of expertise. Often the problem is subtle and occasionally very gross, as in this case. Such designs are bad because these violate the Liskov Substitution Principle. The internet has many good articles explaining this concept e.g., http://en.wikipedia.org/wiki/Liskov_substitution_principle

正如每个人都指出的那样,一队球员不是球员的名单。这个错误是由世界各地的许多人犯的,可能是不同专业水平的人。通常情况下,问题很微妙,有时也很严重,就像这种情况。这样的设计是不好的,因为它们违反了Liskov替换原则。互联网上有很多很好的文章来解释这个概念,比如http://en.wikipedia.org/wiki/liskov_replaction_principle

In summary, there are two rules to be preserved in a Parent/Child relationship among classes:

综上所述,在类之间的父/子关系中有两个规则需要保留:

  • a Child should require no characteristic less than what completely defines the Parent.
  • 一个孩子不应该要求比完全定义他的父母更少的特征。
  • a Parent should require no characteristic in addition to what completely defines the Child.
  • 除了完全定义子元素之外,父元素不需要任何其他特性。

In other words, a Parent is a necessary definition of a child, and a child is a sufficient definition of a Parent.

换句话说,父类是子类的必要定义,子类是父类的充分定义。

Here is a way to think through ones solution and apply the above principle that should help one avoid such a mistake. One should test ones hypothesis by verifying if all the operations of a parent class are valid for the derived class both structurally and semantically.

这里有一个方法来思考一个人的解决方案,并应用上面的原则,应该帮助一个人避免这样的错误。通过验证父类的所有操作在结构上和语义上是否对派生类都有效,应该测试一个假设。

  • Is a football team a list of football players? ( Do all properties of a list apply to a team in the same meaning)
    • Is a team a collection of homogenous entities? Yes, team is a collection of Players
    • 一个团队是一个同质实体的集合吗?是的,球队是由球员组成的
    • Is the order of inclusion of players descriptive of the state of the team and does the team ensure that the sequence is preserved unless explicitly changed? No, and No
    • 球员加入的顺序是否描述了球队的状态?如果没有明确的改变,球队是否保证了顺序的保留?没有,没有
    • Are players expected to be included/dropped based on their sequencial position in the team? No
    • 球员是否会因为他们在球队的位置而被包括/被取消?没有
  • 足球队是足球运动员的名单吗?(一个列表的所有属性是否都适用于一个团队)一个团队是否是一个同质实体的集合?是的,团队是一组球员的集合,是对球员加入的顺序的描述,描述了球队的状态,团队是否保证了顺序的保留,除非有明确的改变?没有,也没有球员会因为他们在球队的位置而被包括/被放弃?没有

As you see, only the first characteristic of a list is applicable to a team. Hence a team is not a list. A list would be a implementation detail of how you manage your team, so it should only be used to store the player objects and be manipulated with methods of Team class.

正如您所看到的,列表的第一个特征只适用于一个团队。因此一个团队不是一个列表。列表将是如何管理您的团队的实现细节,因此它应该只用于存储播放器对象,并使用team类的方法进行操作。

At this point I'd like to remark that a Team class should, in my opinion, not even be implemented using a List; it should be implemented using a Set data structure (HashSet, for example) in most cases.

在这一点上,我想指出,在我看来,团队类甚至不应该使用列表来实现;在大多数情况下,应该使用Set数据结构(例如HashSet)实现它。

#7


32  

What if the FootballTeam has a reserves team along with the main team?

如果足球队和主队都有预备队怎么办?

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

How would you model that with?

你要怎么做模型呢?

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

The relationship is clearly has a and not is a.

这种关系显然有a而不是a。

or RetiredPlayers?

还是RetiredPlayers ?

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

As a rule of thumb, if you ever want to inherit from a collection, name the class SomethingCollection.

根据经验,如果您想要从集合继承,请将类命名为SomethingCollection。

Does your SomethingCollection semantically make sense? Only do this if your type is a collection of Something.

你的某物收藏在语义上有意义吗?只有当您的类型是某物的集合时才这样做。

In the case of FootballTeam it doesn't sound right. A Team is more than a Collection. A Team can have coaches, trainers, etc as the other answers have pointed out.

对于足球队来说,这听起来不太对。一个团队不仅仅是一个集合。一个团队可以有教练,教练,等等,正如其他答案所指出的那样。

FootballCollection sounds like a collection of footballs or maybe a collection of football paraphernalia. TeamCollection, a collection of teams.

足球收集听起来像是足球的集合体,也可能是足球用品的收藏。团队集合,团队的集合。

FootballPlayerCollection sounds like a collection of players which would be a valid name for a class that inherits from List<FootballPlayer> if you really wanted to do that.

足球运动员的收藏听起来就像是一群球员的集合,如果你真的想这么做的话,这将是一个继承自列表 <足球运动员> 的职业的有效名称。

Really List<FootballPlayer> is a perfectly good type to deal with. Maybe IList<FootballPlayer> if you are returning it from a method.

真正的名单 <足球运动员> 是一个完美的类型来处理。也许《足球运动员>》,如果你从一个方法返回。

In summary

总之

Ask yourself

问问你自己

  1. Is X a Y? or Has X a Y?

    X是Y吗?或者X a Y?

  2. Do my class names mean what they are?

    我的类名是什么意思?

#8


26  

First of all, it has to do with usability. If you use inheritance, the Team class will expose behavior (methods) that are designed purely for object manipulation. For example, AsReadOnly() or CopyTo(obj) methods make no sense for the team object. Instead of the AddRange(items) method you would probably want a more descriptive AddPlayers(players) method.

首先,它与可用性有关。如果您使用继承,那么Team类将公开纯粹为对象操作而设计的行为(方法)。例如,AsReadOnly()或CopyTo(obj)方法对team对象没有意义。与其使用AddRange(项目)方法,不如使用更具描述性的addplayer(玩家)方法。

If you want to use LINQ, implementing a generic interface such as ICollection<T> or IEnumerable<T> would make more sense.

如果您想使用LINQ,实现一个通用接口,比如ICollection 或IEnumerable 会更有意义。

As mentioned, composition is the right way to go about it. Just implement a list of players as a private variable.

如前所述,合成是正确的方法。只需将参与者列表实现为私有变量。

#9


24  

Design > Implementation

What methods and properties you expose is a design decision. What base class you inherit from is an implementation detail. I feel it's worth taking a step back to the former.

您公开的方法和属性是设计决策。继承的基类是实现细节。我觉得有必要向前者退一步。

An object is a collection of data and behaviour.

对象是数据和行为的集合。

So your first questions should be:

所以你的第一个问题应该是:

  • What data does this object comprise in the model I'm creating?
  • 这个对象在我创建的模型中包含什么数据?
  • What behaviour does this object exhibit in that model?
  • 这个对象在模型中展示了什么行为?
  • How might this change in future?
  • 这种情况在未来会如何改变?

Bear in mind that inheritance implies an "isa" (is a) relationship, whereas composition implies a "has a" (hasa) relationship. Choose the right one for your situation in your view, bearing in mind where things might go as your application evolves.

记住,继承意味着“isa”(是a)关系,而组合意味着“有”(依萨)关系。在您的视图中为您的情况选择一个合适的,考虑到随着应用程序的发展,事情可能会发展到什么程度。

Consider thinking in interfaces before you think in concrete types, as some people find it easier to put their brain in "design mode" that way.

在考虑具体类型之前,先考虑在接口中进行思考,因为有些人发现这样更容易将他们的大脑置于“设计模式”中。

This isn't something everyone does consciously at this level in day to day coding. But if you're mulling this sort of topic, you're treading in design waters. Being aware of it can be liberating.

这不是每个人在日常编码中有意识地做的事情。但是,如果你在考虑这类话题,你就会在设计领域有所作为。意识到它可以是一种解放。

Consider Design Specifics

Take a look at List<T> and IList<T> on MSDN or Visual Studio. See what methods and properties they expose. Do these methods all look like something someone would want to do to a FootballTeam in your view?

看看MSDN或Visual Studio上的列表 和IList 。查看它们公开哪些方法和属性。在你看来,这些方法都像是某人想要对一支足球队做的事情吗?

Does footballTeam.Reverse() make sense to you? Does footballTeam.ConvertAll<TOutput>() look like something you want?

对你来说足球队有意义吗? ()看起来像你想要的吗?

This isn't a trick question; the answer might genuinely be "yes". If you implement/inherit List<Player> or IList<Player>, you're stuck with them; if that's ideal for your model, do it.

这不是一个难题;答案可能是“是”。如果你执行/继承列表 或IList ,你会被他们卡住;如果这对你的模型来说是理想的,那就去做。

If you decide yes, that makes sense, and you want your object to be treatable as a collection/list of players (behaviour), and you therefore want to implement ICollection or IList, by all means do so. Notionally:

如果您决定是,这是有意义的,并且您希望您的对象可以作为一个参与者(行为)的集合/列表来处理,因此您希望实现ICollection或IList,无论如何都要这样做。名义上的:

class FootballTeam : ... ICollection<Player>
{
    ...
}

If you want your object to contain a collection/list of players (data), and you therefore want the collection or list to be a property or member, by all means do so. Notionally:

如果您希望您的对象包含玩家(数据)的集合/列表,因此您希望集合或列表是属性或成员,那么务必这样做。名义上的:

class FootballTeam ...
{
    public ICollection<Player> Players { get { ... } }
}

You might feel that you want people to be able to only enumerate the set of players, rather than count them, add to them or remove them. IEnumerable<Player> is a perfectly valid option to consider.

您可能会觉得,您希望人们能够只列出一组参与者,而不是对他们进行计数、添加或删除。IEnumerable 是完全有效的选择。

You might feel that none of these interfaces are useful in your model at all. This is less likely (IEnumerable<T> is useful in many situations) but it's still possible.

您可能觉得这些接口在您的模型中没有一个是有用的。这是不太可能的(IEnumerable 在很多情况下是有用的),但它仍然是可能的。

Anyone who attempts to tell you that one of these it is categorically and definitively wrong in every case is misguided. Anyone who attempts to tell you it is categorically and definitively right in every case is misguided.

任何试图告诉你其中一个是绝对错误的,在任何情况下都是错误的。任何试图告诉你的人都是绝对正确的,在任何情况下都是被误导的。

Move on to Implementation

Once you've decided on data and behaviour, you can make a decision about implementation. This includes which concrete classes you depend on via inheritance or composition.

一旦您决定了数据和行为,您就可以决定实现。这包括通过继承或组合依赖哪些具体类。

This may not be a big step, and people often conflate design and implementation since it's quite possible to run through it all in your head in a second or two and start typing away.

这可能不是很大的一步,而且人们经常将设计和实现合并在一起,因为很有可能在一两秒钟内在你的头脑中完成所有这些,然后开始输入。

A Thought Experiment

An artificial example: as others have mentioned, a team is not always "just" a collection of players. Do you maintain a collection of match scores for the team? Is the team interchangable with the club, in your model? If so, and if your team isa collection of players, perhaps it also isa collection of staff and/or a collection of scores. Then you end up with:

一个人为的例子:正如其他人所提到的,一个团队并不总是“只是”一个参与者的集合。你是否为球队保留了比赛分数的集合?在你的模型中,团队是否可以与俱乐部交换?如果是这样的话,如果你的球队是球员的集合,那么它也可能是员工的集合或者分数的集合。然后你得到:

class FootballTeam : ... ICollection<Player>, 
                         ICollection<StaffMember>,
                         ICollection<Score>
{
    ....
}

Design notwithstanding, at this point in C# you won't be able to implement all of these by inheriting from List<T> anyway, since C# "only" supports single inheritance. (If you've tried this malarky in C++, you may consider this a Good Thing.) Implementing one collection via inheritance and one via composition is likely to feel dirty. And properties such as Count become confusing to users unless you implement ILIst<Player>.Count and IList<StaffMember>.Count etc. explicitly, and then they're just painful rather than confusing. You can see where this is going; gut feeling whilst thinking down this avenue may well tell you it feels wrong to head in this direction (and rightly or wrongly, your colleagues might also if you implemented it this way!)

尽管进行了设计,在c#中,您仍然无法通过继承List 来实现所有这些,因为c#“only”支持单个继承。(如果您已经在c++中尝试过这种malarky,您可能会认为这是一件好事。)通过继承实现一个集合,通过组合实现一个集合可能会感觉很脏。如果不实现ILIst ,则Count等属性会让用户感到困惑。计数和IList <职员> 。明确地数等等,然后他们只是痛苦而不是困惑。你可以看到它的走向;当你沿着这条路走下去的时候,直觉很可能会告诉你,朝这个方向走是不对的(无论正确与否,你的同事可能也会这么做!)

The Short Answer (Too Late)

The guideline about not inheriting from collection classes isn't C# specific, you'll find it in many programming languages. It is received wisdom not a law. One reason is that in practice composition is considered to often win out over inheritance in terms of comprehensibility, implementability and maintainability. It's more common with real world / domain objects to find useful and consistent "hasa" relationships than useful and consistent "isa" relationships unless you're deep in the abstract, most especially as time passes and the precise data and behaviour of objects in code changes. This shouldn't cause you to always rule out inheriting from collection classes; but it may be suggestive.

不从集合类继承的指导原则不是c#特定的,您可以在许多编程语言中找到它。这是公认的智慧,不是法律。一个原因是在实践中,组合常常被认为在可理解性、可实现性和可维护性方面胜过继承。在现实世界/域对象中,找到有用的、一致的“哈萨”关系比找到有用的、一致的“isa”关系更常见,除非您深入抽象,特别是随着时间的流逝以及代码中对象的精确数据和行为的变化。这不应该导致您总是排除继承集合类;但它可能具有启发性。

#10


21  

A football team is not a list of football players. A football team is composed of a list of football players!

足球队不是足球运动员的名单。一个足球队是由一群足球运动员组成的!

This is logically wrong:

这是逻辑错误:

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

and this is correct:

这是正确的:

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

#11


18  

It depends on the context

When you consider your team as a list of players, you are projecting the "idea" of a foot ball team down to one aspect: You reduce the "team" to the people you see on the field. This projection is only correct in a certain context. In a different context, this might be completely wrong. Imagine you want to become a sponsor of the team. So you have to talk to the managers of the team. In this context the team is projected to the list of its managers. And these two lists usually don't overlap very much. Other contexts are the current versus the former players, etc.

当你把你的球队看成是一个球员的列表时,你是在把足球队的“想法”投射到一个方面:你把“团队”缩小到你在场上看到的人身上。这种预测只有在一定的背景下才正确。在另一种情况下,这可能是完全错误的。假设你想成为团队的赞助商。所以你得和团队的经理谈谈。在这种情况下,团队被投射到经理的列表中。这两个列表通常没有重叠。其他的上下文是当前和以前的玩家,等等。

Unclear semantics

So the problem with considering a team as a list of its players is that its semantic depends on the context and that it cannot be extended when the context changes. Additionally it is hard to express, which context you are using.

因此,将一个团队视为其参与者列表的问题在于,它的语义依赖于上下文,并且当上下文发生变化时,它不能被扩展。此外,它很难表达,您正在使用的上下文。

Classes are extensible

When you using a class with only one member (e.g. IList activePlayers), you can use the name of the member (and additionally its comment) to make the context clear. When there are additional contexts, you just add an additional member.

当您使用只有一个成员(例如IList activeplayer)的类时,您可以使用成员的名称(以及它的注释)来明确上下文。当有其他上下文时,您只需添加一个额外的成员。

Classes are more complex

In some cases it might be overkill to create a extra class. Each class definition must be loaded through the classloader and will be cached by the virtual machine. This costs you runtime performance and memory. When you have a very specific context it might be OK to consider a football team as a list of players. But in this case, you should really just use a IList , not a class derived from it.

在某些情况下,创建一个额外的类可能有些过分。每个类定义都必须通过类加载器加载,并由虚拟机缓存。这将消耗运行时性能和内存。当你有一个非常具体的背景时,考虑一个足球队作为球员名单是可以的。但是在这种情况下,你应该使用IList,而不是从它派生的类。

Conclusion / Considerations

When you have a very specific context, it is OK to consider a team as a list of players. For example inside a method it is completely OK to write

当你有一个非常特定的背景时,可以将一个团队看作是一个球员列表。例如,在方法内部,完全可以编写

IList<Player> footballTeam = ...

When using F#, it can even be OK to create a type abbreviation

在使用f#时,甚至可以创建一个类型缩写。

type FootballTeam = IList<Player>

But when the context is broader or even unclear, you should not do this. This is especially the case, when you create a new class, where it is not clear in which context it may be used in the future. A warning sign is when you start to add additional attributes to your class (name of the team, coach, etc.). This is a clear sign that the context where the class will be used is not fixed and will change in the future. In this case you cannot consider the team as a list of players, but you should model the list of the (currently active, not injured, etc.) players as an attribute of the team.

但当背景更广泛或甚至不清楚时,你不应该这么做。当您创建一个新类时,尤其如此,在这种情况下,不清楚将来在什么上下文中可以使用它。警告标志是当您开始向您的类添加附加属性时(团队名称、教练等)。这是一个明确的信号,表明将使用类的上下文不是固定的,将来会发生变化。在这种情况下,您不能将团队视为一个球员列表,但是您应该将(当前活动的,而不是受伤的,等等)球员列表作为团队的属性进行建模。

#12


18  

Let me rewrite your question. so you might see the subject from a different perspective.

让我重写一下你的问题。所以你可能会从不同的角度看待这个问题。

When I need to represent a football team, I understand that it is basically a name. Like: "The Eagles"

当我需要代表一个足球队时,我知道它基本上就是一个名字。如:“老鹰”

string team = new string();

Then later I realized teams also have players.

后来我意识到球队也有球员。

Why can't I just extend the string type so that it also holds a list of players?

为什么我不能扩展字符串类型,让它也包含玩家列表?

Your point of entry into the problem is arbitrary. Try to think what does a team have (properties), not what it is.

你对这个问题的切入点是任意的。试着思考一个团队有什么(属性),而不是它是什么。

After you do that, you could see if it shares properties with other classes. And think about inheritance.

在您这样做之后,您可以看到它是否与其他类共享属性。想想继承。

#13


13  

Does allowing people to say

允许人们说吗

myTeam.subList(3, 5);

make any sense at all? If not then it shouldn't be a List.

有什么意义吗?如果不是,那就不应该是列表。

#14


13  

Just because I think the other answers pretty much go off on a tangent of whether a football team "is-a" List<FootballPlayer> or "has-a" List<FootballPlayer>, which really doesn't answer this question as written.

仅仅因为我认为其他的答案都与一个足球队的“a”或“hasa”有关,这并不能回答这个问题。

The OP chiefly asks for clarification on guidelines for inheriting from List<T>:

OP主要要求明确从List 继承指南:

A guideline says that you shouldn't inherit from List<T>. Why not?

一个指导原则说你不应该继承List 。为什么不呢?

Because List<T> has no virtual methods. This is less of a problem in your own code, since you can usually switch out the implementation with relatively little pain - but can be a much bigger deal in a public API.

因为List 没有虚方法。在您自己的代码中,这并不是什么问题,因为您通常可以轻松地切换实现——但是在公共API中,这可能是一件大事。

What is a public API and why should I care?

什么是公共API,我为什么要关心?

A public API is an interface you expose to 3rd party programmers. Think framework code. And recall that the guidelines being referenced are the ".NET Framework Design Guidelines" and not the ".NET Application Design Guidelines". There is a difference, and - generally speaking - public API design is a lot more strict.

公共API是向第三方程序员公开的接口。认为框架代码。记住,参考的指导方针是"netframework设计指南“而不是”。网络应用程序设计指南”。这是有区别的,一般来说,公共API设计要严格得多。

If my current project does not and is not likely to ever have this public API, can I safely ignore this guideline? If I do inherit from List and it turns out I need a public API, what difficulties will I have?

如果我当前的项目没有并且不太可能有这个公共API,我可以安全地忽略这个指导方针吗?如果我继承了List并且需要一个公共API,会有什么困难?

Pretty much, yeah. You may want to consider the rationale behind it to see if it applies to your situation anyway, but if you're not building a public API then you don't particularly need to worry about API concerns like versioning (of which, this is a subset).

差不多,是的。您可能需要考虑它背后的基本原理,看看它是否适用于您的情况,但是如果您不构建一个公共API,那么您就不需要特别担心像版本控制这样的API问题(这是一个子集)。

If you add a public API in the future, you will either need to abstract out your API from your implementation (by not exposing your List<T> directly) or violate the guidelines with the possible future pain that entails.

如果您在将来添加一个公共API,您将需要从实现中抽象出您的API(通过不直接公开您的列表 ),或者违反指导方针,可能会带来未来的痛苦。

Why does it even matter? A list is a list. What could possibly change? What could I possibly want to change?

这有什么关系呢?列表就是一个列表。什么能改变吗?我能改变什么呢?

Depends on the context, but since we're using FootballTeam as an example - imagine that you can't add a FootballPlayer if it would cause the team to go over the salary cap. A possible way of adding that would be something like:

这取决于背景,但既然我们是用足球队作为例子,假设你不能增加一个足球运动员,如果它会导致球队越过工资帽。可能的增加方式是:

 class FootballTeam : List<FootballPlayer> {
     override void Add(FootballPlayer player) {
        if (this.Sum(p => p.Salary) + player.Salary > SALARY_CAP)) {
          throw new InvalidOperationException("Would exceed salary cap!");
        }
     }
 }

Ah...but you can't override Add because it's not virtual (for performance reasons).

啊…但是您不能重写Add,因为它不是虚拟的(出于性能原因)。

If you're in an application (which, basically, means that you and all of your callers are compiled together) then you can now change to using IList<T> and fix up any compile errors:

如果您在一个应用程序中(基本上,这意味着您和所有调用者都被编译在一起),那么您现在可以更改为使用IList 并修复任何编译错误:

 class FootballTeam : IList<FootballPlayer> {
     private List<FootballPlayer> Players { get; set; }

     override void Add(FootballPlayer player) {
        if (this.Players.Sum(p => p.Salary) + player.Salary > SALARY_CAP)) {
          throw new InvalidOperationException("Would exceed salary cap!");
        }
     }
     /* boiler plate for rest of IList */
 }

but, if you've publically exposed to a 3rd party you just made a breaking change that will cause compile and/or runtime errors.

但是,如果您公开暴露给第三方,您刚刚做了一个破坏性的更改,将导致编译和/或运行时错误。

TL;DR - the guidelines are for public APIs. For private APIs, do what you want.

TL;DR -指南是针对公共api的。对于私有api,您想做什么就做什么。

#15


11  

It depends on the behaviour of your "team" object. If it behaves just like a collection, it might be OK to represent it first with a plain List. Then you might start to notice that you keep duplicating code that iterates on the list; at this point you have the option of creating a FootballTeam object that wraps the list of players. The FootballTeam class becomes the home for all the code that iterates on the list of players.

这取决于“团队”对象的行为。如果它的行为就像一个集合,那么可以先用一个普通的列表来表示它。然后您可能会注意到,您保留了迭代列表上的重复代码;此时,您可以选择创建一个足球团队对象来包装球员列表。足球队成为了球员名单上所有代码的家。

It makes my code needlessly verbose. I must now call my_team.Players.Count instead of just my_team.Count. Thankfully, with C# I can define indexers to make indexing transparent, and forward all the methods of the internal List... But that's a lot of code! What do I get for all that work?

它使我的代码不必要地冗长。我现在必须呼叫my_team.Players。计数,而不是my_team.Count。值得庆幸的是,使用c#我可以定义索引器,使索引变得透明,并转发内部列表的所有方法……但那是很多的代码!做了那么多工作,我能得到什么?

Encapsulation. Your clients need not know what goes on inside of FootballTeam. For all your clients know, it might be implemented by looking the list of players up in a database. They don't need to know, and this improves your design.

封装。你的客户不需要知道球队内部发生了什么。对于所有您的客户来说,它可以通过在数据库中查找玩家列表来实现。他们不需要知道,这改进了你的设计。

It just plain doesn't make any sense. A football team doesn't "have" a list of players. It is the list of players. You don't say "John McFootballer has joined SomeTeam's players". You say "John has joined SomeTeam". You don't add a letter to "a string's characters", you add a letter to a string. You don't add a book to a library's books, you add a book to a library.

这是毫无意义的。一个足球队没有球员名单。这是球员名单。你不会说“约翰·麦克球员加入了某个队的球员”。你说“约翰加入了一个团队”。你不向“字符串的字符”添加一个字母,而是向字符串添加一个字母。你不是把一本书加到图书馆的书里,而是把一本书加到图书馆里。

Exactly :) you will say footballTeam.Add(john), not footballTeam.List.Add(john). The internal list will not be visible.

确切地说:)你会说成足球队。add(约翰),而不是football . list . add(约翰)。内部列表将不可见。

#16


10  

What is the correct C# way of representing a data structure...

c#表示数据结构的正确方式是什么?

Remeber, "All models are wrong, but some are useful." -George E. P. Box

记住,“所有的模型都是错误的,但有些是有用的。”——e . p .盒子

There is no a "correct way", only a useful one.

没有“正确的方法”,只有有用的方法。

Choose one that is useful to you and/your users. That's it. Develop economically, don't over-engineer. The less code you write, the less code you will need to debug. (read the following editions).

选择一个对你和你的用户有用的。就是这样。发展经济,不过度工程化。编写的代码越少,需要调试的代码就越少。(阅读下面的版本)。

-- Edited

——编辑

My best answer would be... it depends. Inheriting from a List would expose the clients of this class to methods that may be should not be exposed, primarily because FootballTeam looks like a business entity.

我最好的答案是……视情况而定。从列表中继承将向该类的客户端公开不应该公开的方法,主要是因为足球队看起来像一个商业实体。

-- Edition 2

——版2

I sincerely don't remember to what I was referring on the “don't over-engineer” comment. While I believe the KISS mindset is a good guide, I want to emphasize that inheriting a business class from List would create more problems than it resolves, due abstraction leakage.

我真的不记得我指的是什么“不要过度设计”的评论。尽管我相信KISS思维模式是一个很好的指南,但我想强调的是,从List继承一个业务类会产生比它所解决的更多的问题,由于抽象泄漏。

On the other hand, I believe there are a limited number of cases where simply to inherit from List is useful. As I wrote in the previous edition, it depends. The answer to each case is heavily influenced by both knowledge, experience and personal preferences.

另一方面,我认为,仅从列表中继承的案例数量有限。正如我在上一版中所写的,这要看情况而定。每个案例的答案都受到知识、经验和个人偏好的严重影响。

Thanks to @kai for helping me to think more precisely about the answer.

感谢@kai帮助我更准确地思考答案。

#17


9  

There are a lot excellent answers here, but I want to touch on something I didn't see mentioned: Object oriented design is about empowering objects.

这里有很多优秀的答案,但我想谈谈我没有提到的东西:面向对象的设计是关于增强对象的。

You want to encapsulate all your rules, additional work and internal details inside an appropriate object. In this way other objects interacting with this one don't have to worry about it all. In fact, you want to go a step further and actively prevent other objects from bypassing these internals.

您希望在适当的对象中封装所有规则、附加工作和内部细节。通过这种方式,与这个对象交互的其他对象不必担心所有这些。实际上,您希望更进一步,积极地防止其他对象绕过这些内部元素。

When you inherit from List, all other objects can see you as a List. They have direct access to the methods for adding and removing players. And you'll have lost your control; for example:

当您从列表继承时,所有其他对象都可以将您视为一个列表。他们可以直接访问添加和删除播放器的方法。你会失去控制;例如:

Suppose you want to differentiate when a player leaves by knowing whether they retired, resigned or were fired. You could implement a RemovePlayer method that takes an appropriate input enum. However, by inheriting from List, you would be unable to prevent direct access to Remove, RemoveAll and even Clear. As a result, you've actually disempowered your FootballTeam class.

假设你想通过知道球员是退役、辞职还是被解雇来区分他们何时离开。您可以实现RemovePlayer方法,该方法接受适当的输入枚举。但是,通过继承List,您将无法阻止直接访问来删除、RemoveAll甚至清除。结果,你实际上剥夺了你的足球队的权利。


Additional thoughts on encapsulation... You raised the following concern:

额外的思想封装…你提出以下关切:

It makes my code needlessly verbose. I must now call my_team.Players.Count instead of just my_team.Count.

它使我的代码不必要地冗长。我现在必须呼叫my_team.Players。用Count代替my_team.Count。

You're correct, that would be needlessly verbose for all clients to use you team. However, that problem is very small in comparison to the fact that you've exposed List Players to all and sundry so they can fiddle with your team without your consent.

您是对的,这对于所有使用您团队的客户来说是不必要的冗长。然而,这个问题与你让名单球员接触到所有人的事实相比是非常小的,这样他们就可以在没有你同意的情况下和你的团队一起玩。

You go on to say:

你接着说:

It just plain doesn't make any sense. A football team doesn't "have" a list of players. It is the list of players. You don't say "John McFootballer has joined SomeTeam's players". You say "John has joined SomeTeam".

这是毫无意义的。一个足球队没有球员名单。这是球员名单。你不会说“约翰·麦克球员加入了某个队的球员”。你说“约翰加入了一个团队”。

You're wrong about the first bit: Drop the word 'list', and it's actually obvious that a team does have players.
However, you hit the nail on the head with the second. You don't want clients calling ateam.Players.Add(...). You do want them calling ateam.AddPlayer(...). And your implemention would (possibly amongst other things) call Players.Add(...) internally.

你在第一点上错了:去掉“列表”这个词,很明显,一个球队确实有球员。然而,你说的对极了。您不希望客户调用ateam.Players.Add(…)。你确实希望他们调用ateam.AddPlayer(…)。你的实现会(可能还有其他东西)调用玩家。


Hopefully you can see how important encapsulation is to the objective of empowering your objects. You want to allow each class to do its job well without fear of interference from other objects.

希望您能够看到封装对于实现授权对象的目标是多么重要。您希望允许每个类出色地完成其工作,而不必担心来自其他对象的干扰。

#18


8  

This reminds me of the "Is a" versus "has a" tradeoff. Sometimes it is easier and makesmore sense to inherit directly from a super class. Other times it makes more sense to create a standalone class and include the class you would have inherited from as a member variable. You can still access the functionality of the class but are not bound to the interface or any other constraints that might come from inheriting from the class.

这让我想起了“是”与“是”之间的权衡。有时直接从超类继承更容易,也更有意义。其他时候,创建一个独立的类更有意义,并且包含从成员变量继承的类。您仍然可以访问类的功能,但不绑定到接口或从类继承而来的任何其他约束。

Which do you do? As with a lot of things...it depends on the context. The guide I would use is that in order to inherit from another class there truly should be an "is a" relationship. So if you a writing a class called BMW, it could inherit from Car because a BMW truly is a car. A Horse class can inherit from the Mammal class because a horse actually is a mammal in real life and any Mammal functionality should be relevant to Horse. But can you say that a team is a list? From what I can tell, it does not seem like a Team really "is a" List. So in this case, I would have a List as a member variable.

,你会怎么做?和很多事情一样……这取决于上下文。我使用的指南是,为了从另一个类继承,应该有一个“is”关系。所以如果你写一个叫做宝马的课程,它可以从汽车继承,因为宝马确实是一辆车。马类可以从哺乳动物类继承,因为马在现实生活中实际上是哺乳动物,任何哺乳动物的功能都应该与马相关。但是你能说一个团队是一个列表吗?据我所知,这并不是一个真正的“团队”。在这种情况下,我将一个列表作为成员变量。

#19


6  

My dirty secret: I don't care what people say, and I do it. .NET Framework is spread with "XxxxCollection" (UIElementCollection for top of my head example).

我的肮脏秘密:我不在乎别人怎么说,我就是这么做的。net框架是用“xxxxxxcollection”(我的head示例的顶部是UIElementCollection)来扩展的。

So what stops me saying:

是什么阻止我说:

team.Players.ByName("Nicolas")

When I find it better than

当我发现它比

team.ByName("Nicolas")

Moreover, my PlayerCollection might be used by other class, like "Club" without any code duplication.

此外,我的PlayerCollection可能被其他类使用,比如“Club”,没有任何代码复制。

club.Players.ByName("Nicolas")

Best practices of yesterday, might not be the one of tomorrow. There is no reason behind most best practices, most are only wide agreement among the community. Instead of asking the community if it will blame you when you do that ask yourself, what is more readable and maintainable?

昨天的最佳实践,可能不是明天的。大多数最佳实践的背后没有任何理由,大多数都只是社区之间的广泛共识。当你这样做的时候,不要问社区是否会责备你,而要问自己,什么东西更容易阅读和维护?

team.Players.ByName("Nicolas") 

or

team.ByName("Nicolas")

Really. Do you have any doubt? Now maybe you need to play with other technical constraints that prevent you to use List<T> in your real use case. But don't add a constraint that should not exist. If Microsoft did not document the why, then it is surely a "best practice" coming from nowhere.

真的。你有什么疑问吗?现在,您可能需要处理其他技术约束,以防止在实际用例中使用List 。但是不要添加不应该存在的约束。如果微软没有记录下原因,那么毫无疑问这是一种“最佳实践”。

#20


6  

What the guidelines say is that the public API should not reveal the internal design decision of whether you are using a list, a set, a dictionary, a tree or whatever. A "team" is not necessarily a list. You may implement it as a list but users of your public API should use you class on a need to know basis. This allows you to change your decision and use a different data structure without affecting the public interface.

指导方针说的是,公共API不应该显示您是否在使用列表、集合、字典、树或其他东西的内部设计决策。一个“团队”不一定是一个列表。您可以将它作为一个列表来实现,但是您的公共API的用户应该根据需要使用您的类。这允许您更改决策并使用不同的数据结构,而不会影响公共接口。

#21


4  

When they say List<T> is "optimized" I think they want to mean that it doesn't have features like virtual methods which are bit more expensive. So the problem is that once you expose List<T> in your public API, you loose ability to enforce business rules or customize its functionality later. But if you are using this inherited class as internal within your project (as opposed to potentially exposed to thousands of your customers/partners/other teams as API) then it may be OK if it saves your time and it is the functionality you want to duplicate. The advantage of inheriting from List<T> is that you eliminate lot of dumb wrapper code that is just never going to be customized in foreseeable future. Also if you want your class to explicitly have exact same semantics as List<T> for the life of your APIs then also it may be OK.

当他们说List 是“优化”的时候,我认为他们想要的是它没有像虚拟方法那样的功能,而这些方法更贵一些。因此,问题是一旦在您的公共API中公开了List ,您就会失去执行业务规则的能力,或者在以后定制它的功能。但是,如果您在项目内部使用这个继承的类(而不是将它作为API公开给成千上万的客户/合作伙伴/其他团队),那么如果它节省了您的时间,并且是您想要复制的功能,那么它可能是可以的。继承List 的好处是,您可以消除许多哑包装器代码,这些代码在可预见的将来永远不会被定制。另外,如果您希望您的类在api的生命周期中具有与List 完全相同的语义,那么它也可以。

I often see lot of people doing tons of extra work just because of FxCop rule says so or someone's blog says it's a "bad" practice. Many times, this turns code in to design pattern palooza weirdness. As with lot of guideline, treat it as guideline that can have exceptions.

我经常看到很多人因为FxCop规则而做了大量的额外工作,或者有人的博客说这是一种“坏”行为。很多时候,这就把代码变成了设计模式的怪异。和许多指导方针一样,把它当作有例外的指导方针。

#22


3  

If your class users need all the methods and properties** List has, you should derive your class from it. If they don't need them, enclose the List and make wrappers for methods your class users actually need.

如果您的类用户需要所有方法和属性**列表,您应该从它派生类。如果它们不需要它们,请将列表括起来,并为类用户实际需要的方法创建包装器。

This is a strict rule, if you write a public API, or any other code that will be used by many people. You may ignore this rule if you have a tiny app and no more than 2 developers. This will save you some time.

这是一个严格的规则,如果您编写一个公共API,或者编写任何其他将被许多人使用的代码。如果你有一个小的应用程序,并且不超过2个开发人员,你可以忽略这个规则。这会节省你一些时间。

For tiny apps, you may also consider choosing another, less strict language. Ruby, JavaScript - anything that allows you to write less code.

对于小型应用程序,您还可以考虑选择另一种不那么严格的语言。Ruby, JavaScript——任何允许你少写代码的东西。

#23


3  

I just wanted to add that Bertrand Meyer, the inventor of Eiffel and design by contract, would have Team inherit from List<Player> without so much as batting an eyelid.

我只是想补充一点,埃菲尔的发明者和合同设计,贝特朗·迈耶会让一队从List 不费吹灰之力就继承下来。

In his book, Object-Oriented Software Construction, he discusses the implementation of a GUI system where rectangular windows can have child windows. He simply has Window inherit from both Rectangle and Tree<Window> to reuse the implementation.

在他的《面向对象的软件构建》一书中,他讨论了GUI系统的实现,矩形窗口可以有子窗口。他只需要从矩形和树 <窗口> 继承窗口来重用实现。

However, C# is not Eiffel. The latter supports multiple inheritance and renaming of features. In C#, when you subclass, you inherit both the interface and the implemenation. You can override the implementation, but the calling conventions are copied directly from the superclass. In Eiffel, however, you can modify the names of the public methods, so you can rename Add and Remove to Hire and Fire in your Team. If an instance of Team is upcast back to List<Player>, the caller will use Add and Remove to modify it, but your virtual methods Hire and Fire will be called.

然而,c#并不是埃菲尔。后者支持多重继承和特性重命名。在c#中,当您子类化时,您继承了接口和实现。您可以重写实现,但是调用约定直接从超类复制。然而,在Eiffel中,您可以修改公共方法的名称,因此您可以重命名Add and Remove来雇佣和解雇您的团队。如果一个Team的实例被上传至列表 ,调用者将使用Add and Remove来修改它,但是您的虚拟方法Hire and Fire将被调用。

#24


2  

While I don't have a complex comparison as most of these answers do, I would like to share my method for handling this situation. By extending IEnumerable<T>, you can allow your Team class to support Linq query extensions, without publicly exposing all the methods and properties of List<T>.

虽然我不像大多数答案那样有一个复杂的比较,但我愿意分享我处理这种情况的方法。通过扩展IEnumerable ,您可以允许您的Team类支持Linq查询扩展,而不公开显示List 的所有方法和属性。

class Team : IEnumerable<Player>
{
    private readonly List<Player> playerList;

    public Team()
    {
        playerList = new List<Player>();
    }

    public Enumerator GetEnumerator()
    {
        return playerList.GetEnumerator();
    }

    ...
}

class Player
{
    ...
}

#25


0  

I think I don't agree with your generalization. A team isn't just a collection of players. A team has so much more information about it - name, emblem, collection of management/admin staff, collection of coaching crew, then collection of players. So properly, your FootballTeam class should have 3 collections and not itself be a collection; if it is to properly model the real world.

我想我不同意你的概括。一个团队不只是一群球员。一个团队有很多关于它的信息-名字,徽章,管理/管理人员的集合,教练组的集合,然后是球员的集合。所以正确地说,你的足球队应该有3个集合,而不是一个集合;如果它是正确地模拟现实世界。

You could consider a PlayerCollection class which like the Specialized StringCollection offers some other facilities - like validation and checks before objects are added to or removed from the internal store.

您可以考虑PlayerCollection类,它类似于专门的StringCollection提供了一些其他功能——比如在对象添加或从内部存储中删除之前进行验证和检查。

Perhaps, the notion of a PlayerCollection betters suits your preferred approach?

也许,玩家集合的概念适合你的偏好?

public class PlayerCollection : Collection<Player> 
{ 
}

And then the FootballTeam can look like this:

然后这个足球队就像这样:

public class FootballTeam 
{ 
    public string Name { get; set; }
    public string Location { get; set; }

    public ManagementCollection Management { get; protected set; } = new ManagementCollection();

    public CoachingCollection CoachingCrew { get; protected set; } = new CoachingCollection();

    public PlayerCollection Players { get; protected set; } = new PlayerCollection();
}