为什么不从列表继承 ?

本文翻译自:Why not inherit from List?

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.. 例如,本赛季的总得分,当前预算,统一的颜色,代表球队名称的string等。

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 ). 好的,一支足球队就像一个球员名单,但是另外,它还有一个名字( string )和总得分(一个int )。 .NET does not provide a class for storing football teams, so I will make my own class. .NET没有提供用于存储足球队的课程,因此我将创建自己的课程。 The most similar and relevant existing structure is List<FootballPlayer> , so I will inherit from it: 最相似和相关的现有结构是List<FootballPlayer> ,所以我将从它继承:

 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> . 但事实证明, 有一条准则规定您不应从List<T>继承 I'm thoroughly confused by this guideline in two respects. 我对该指南在两个方面完全感到困惑。

Why not? 为什么不?

Apparently List is somehow optimized for performance . 显然List在某种程度上针对性能进行了优化 How so? 怎么会这样? What performance problems will I cause if I extend List ? 如果我扩展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" . 我看到的另一个原因是List由Microsoft提供,并且我无法控制它,因此以后在公开“ public API”之后就无法更改它 But I struggle to understand this. 但是我很难理解这一点。 What is a public API and why should I care? 什么是公共API,我为什么要关心? If my current project does not and is not likely to ever have this public API, can I safely ignore this guideline? 如果我当前的项目没有并且不太可能拥有此公共API,那么我可以安全地忽略此指南吗? If I do inherit from List and it turns out I need a public API, what difficulties will I have? 如果我确实从List继承而来但事实证明我需要一个公共API,我会遇到什么困难?

Why does it even matter? 为什么如此重要? A list is a list. 列表就是列表。 What could possibly change? 有什么可能改变? What could I possibly want to change? 我可能要更改什么?

扫描二维码关注公众号,回复: 10405609 查看本文章

And lastly, if Microsoft did not want me to inherit from List , why didn't they make the class sealed ? 最后,如果Microsoft不希望我从List继承,他们为什么不将类sealed

What else am I supposed to use? 我还应该使用什么?

Apparently, for custom collections, Microsoft has provided a Collection class which should be extended instead of List . 显然,对于自定义集合,Microsoft提供了Collection类,应该扩展该类而不是List But this class is very bare, and does not have many useful things, such as AddRange , for instance. 但是此类非常裸露,并且没有很多有用的东西, 例如AddRange jvitor83's answer provides a performance rationale for that particular method, but how is a slow AddRange not better than no AddRange ? jvitor83的答案提供了该特定方法的性能原理,但是慢速AddRange怎么比没有AddRange更好呢?

Inheriting from Collection is way more work than inheriting from List , and I see no benefit. Collection继承要比从List继承做更多的工作,我看不出任何好处。 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. 当然,Microsoft不会无缘无故地告诉我做额外的工作,因此我不禁感到自己在某种程度上误解了某些东西,而继承Collection实际上不是解决我问题的正确方法。

I've seen suggestions such as implementing IList . 我已经看到了实现IList建议。 Just no. 就是不行。 This is dozens of lines of boilerplate code which gains me nothing. 这是几十行样板代码,对我毫无帮助。

Lastly, some suggest wrapping the List in something: 最后,有些人建议将List包装在以下内容中:

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 . 我现在必须调用my_team.Players.Count而不是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! 幸运的是,使用C#,我可以定义索引器以使索引透明化,并转发内部List所有方法...但是,这需要很多代码! What do I get for all that work? 我能从所有工作中得到什么?

  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". 您不会说“ John McFootballer已加入SomeTeam的球员”。 You say "John has joined SomeTeam". 您说“约翰加入了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#表示数据结构的正确方法,“逻辑上”(也就是说,“对人类而言”)只是一小部分thingslist

Is inheriting from List<T> always unacceptable? List<T>继承始终是不可接受的吗? When is it acceptable? 什么时候可以接受? Why/why not? 为什么/为什么不呢? What must a programmer consider, when deciding whether to inherit from List<T> or not? 程序员在决定是否从List<T>继承时必须考虑什么?


#1楼

参考:https://stackoom.com/question/1T185/为什么不从列表继承-T


#2楼

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. 如果使用继承,则Team类将公开纯粹用于对象操作的行为(方法)。 For example, AsReadOnly() or CopyTo(obj) methods make no sense for the team object. 例如,对于团队对象, AsReadOnly()CopyTo(obj)方法没有任何意义。 Instead of the AddRange(items) method you would probably want a more descriptive AddPlayers(players) method. 代替AddRange(items)方法,您可能需要更具描述性的AddPlayers(players)方法。

If you want to use LINQ, implementing a generic interface such as ICollection<T> or IEnumerable<T> would make more sense. 如果要使用LINQ,则实现通用接口(例如ICollection<T>IEnumerable<T>会更有意义。

As mentioned, composition is the right way to go about it. 如前所述,合成是正确的方法。 Just implement a list of players as a private variable. 只需将玩家列表实现为私有变量即可。


#3楼

Wow, your post has an entire slew of questions and points. 哇,您的帖子中有很多问题和观点。 Most of the reasoning you get from Microsoft is exactly on point. 您从Microsoft获得的大多数理由都是正确的。 Let's start with everything about List<T> 让我们从有关List<T>一切开始

  • List<T> is highly optimized. List<T> 高度优化。 Its main usage is to be used as a private member of an object. 它的主要用法是用作对象的私有成员。
  • 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>> { ... } . Microsoft并未密封它,因为有时您可能想创建一个名称更友好的class MyList<T, TX> : List<CustomObject<T, Something<TX>> { ... } Now it's as easy as doing var list = new MyList<int, string>(); 现在,就像执行var list = new MyList<int, string>(); .
  • 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. CA1002:请勿公开通用列表 :基本上,即使您打算将这个应用程序用作唯一的开发人员,也值得通过良好的编码实践进行开发,因此它们会灌输给您和您的本性。 You are still allowed to expose the list as an IList<T> if you need any consumer to have an indexed list. 如果您需要任何使用者具有索引列表,则仍然可以将列表公开为IList<T> This let's you change the implementation within a class later on. 这使您稍后可以在类中更改实现。
  • Microsoft made Collection<T> very generic because it is a generic concept... the name says it all; 微软使Collection<T>非常通用,因为它是一个通用概念。 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> . 有更精确的版本,例如SortedCollection<T>ObservableCollection<T>ReadOnlyCollection<T>等,每个版本都实现IList<T>不实现 List<T>
  • Collection<T> allows for members (ie Add, Remove, etc.) to be overridden because they are virtual. Collection<T>允许重写成员(例如,Add,Remove等),因为它们是虚拟的。 List<T> does not. List<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楼

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 . List<T>是一种机制 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> . 因此,使一个名为Roster的属性成为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. 并在使用时将其设置为ReadOnlyList<Player> ,除非您认为知道足球队的每个人都将从名单中删除球员。

Is inheriting from List<T> always unacceptable? List<T>继承始终是不可接受的吗?

Unacceptable to who? 谁不能接受? Me? 我? No. 没有。

When is it acceptable? 什么时候可以接受?

When you're building a mechanism that extends the List<T> mechanism . 在构建扩展List<T>机制的机制时

What must a programmer consider, when deciding whether to inherit from List<T> or not? 程序员在决定是否从List<T>继承时必须考虑什么?

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. 您花了更多时间输入问题,这将使您为List<T>的相关成员编写转发方法超过50次。 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. 这是几分钟的工作。

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?" 我不知道您在这堂课上的业务案例是什么,但是如果我有一堂代表足球队的课,我想问一下这样的问题:“有多少海鹰球员因2003年至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? " 或“ 今年猪头人一路走?

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. 就是说,在我看来,一支足球队的模型被很好地模拟为历史事实的集合,例如球员被招募,受伤,退休等的历史事实 。显然,目前的球员名册是一个重要的事实,应该很重要。居中,但您可能还需要对这个对象进行其他有趣的操作,这些操作需要更多的历史视角。


#5楼

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 (eg IList activePlayers ), you can use the name of the member (and additionally its comment) to make the context clear. 当您使用仅具有一个成员的类(例如IList activePlayers )时,可以使用成员的名称(以及其注释)来使上下文清晰。 When there are additional contexts, you just add an additional member. 如果有其他上下文,则只需添加一个其他成员。

Classes are more complex 类更复杂

In some cases it might be overkill to create an 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 whose context in which it may be used in the future is not clear. 当您创建一个新类时,尤其是在这种情况下,将来可能会在哪个上下文中使用该类尚不清楚。 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. 在这种情况下,您不能将团队视为球员列表,但应将(当前活跃,未受伤等)球员列表建模为团队的属性。


#6楼

This is a classic example of composition vs inheritance . 这是组成继承的经典示例。

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. 无论是否需要/全部,您都会获得所有List方法。

  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. 例如,您可能要创建一个通用的Team对象,然后让BaseballTeam,FootballTeam等从该对象继承。 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. 要从List继承,您需要从Team继承,但这意味着所有各种类型的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. 两者都有其用途,但这是明显的情况,其中优选组合物。

发布了0 篇原创文章 · 获赞 136 · 访问量 83万+

猜你喜欢

转载自blog.csdn.net/xfxf996/article/details/105244656