问题:

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:

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.

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?

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?

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?

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.

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

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?

  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.

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?

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?

解答  Eric Lippert

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?

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.

Is inheriting from List<T> always unacceptable?

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?

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.

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?"

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.

解答 迪米特原则

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

解答 组合由于继承

第一种

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<T> is highly optimized. 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>> { ... }. Now it's as easy as doing 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. 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.
  • 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<T> allows for members (i.e. Add, Remove, etc.) to be overridden because they are virtual. List<T> does not.
  • 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>(); 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.
}

第二种

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.

  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.

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.

扩展阅读

Define class with itself as generic implementation. Why/how does this work?

Why not inherit from List<T>?的更多相关文章

  1. 谈谈一些有趣的CSS题目(四)-- 从倒影说起,谈谈 CSS 继承 inherit

    开本系列,讨论一些有趣的 CSS 题目,抛开实用性而言,一些题目为了拓宽一下解决问题的思路,此外,涉及一些容易忽视的 CSS 细节. 解题不考虑兼容性,题目天马行空,想到什么说什么,如果解题中有你感觉 ...

  2. height:100%与height:inherit的区别

    一.兼容性 首先,inherit这个属性只是在ie8+才支持:100%支持ie6: 二.大多数情况下没有区别 在正常情况下height:100%与height:inherit没有任何区别: 1.父元素 ...

  3. 从倒影说起,谈谈 CSS 继承 inherit(转)

    从倒影说起,谈谈 CSS 继承 inherit 给定一张有如下背景图的 div: 制作如下的倒影效果: 方法很多,但是我们当然要寻找最快最便捷的方法,至少得是无论图片怎么变化,div 大小怎么变化,我 ...

  4. initial、inherit、unset、revert和all

    前面的话 在CSS中,有4个关键字理论上可以应用于任何的CSS属性,它们是initial(初始).inherit(继承).unset(未设置).revert(还原).而all的取值只能是以上这4个关键 ...

  5. 使用inherit属性值继承其父元素样式来覆盖UA自带样式。

    像button.input这样的表单控件,不同的浏览器都会有自己的样式风格(UA样式).我们可以使用inherit继承其父元素样式,从而覆盖浏览器的UA样式. button, input, selec ...

  6. inherit与auto

    大家是不是和我一样,在刚开始学习css的时候,在css文件开头是不是经常看到这样的代码: * {margin:0 px; padding:0 px;}  . 在接下来设置颜色字体时在body元素的cs ...

  7. [转]关于position 的 static、relative、absolute、fixed、inherit

    本文转自:http://www.56770.com/faq/list/?id=410 position 有五个值:static.relative.absolute.fixed.inherit. sta ...

  8. CSS的inherit与auto使用分析

    一个很显浅的寓言,千年老树,电打雷劈,屹立不倒,却毁于蝼蚁的侵袭之下.自以为精通CSS的人,常常被一些小问题搞到头晕脑胀. 通常是一个很小的数值,经过层层放大歪曲后,整个布局就走形了.CSS是一门很简 ...

  9. C# inherit

    Case:class A has a construct. class B is inherit from class A and B also has a construct. What's the ...

  10. 谈谈一些有趣的CSS题目(十五)-- 谈谈 CSS 关键字 initial、inherit 和 unset

    开本系列,谈谈一些有趣的 CSS 题目,题目类型天马行空,想到什么说什么,不仅为了拓宽一下解决问题的思路,更涉及一些容易忽视的 CSS 细节. 解题不考虑兼容性,题目天马行空,想到什么说什么,如果解题 ...

随机推荐

  1. greenplum-cc-web4.0监控安装

    简介: 本文是基于greenplum5.7,greenplum-cc-web4.0安装的. 一.安装greenplum监控的数据库以及创建用户(在gpadmin用户下安装) 1.开启greenplum ...

  2. hdu2094—看似拓扑实际上是一道思维题

    HDU2094  产生冠军 题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2094 题意:中文题,就不解释了.题意已经非常清楚了. 这道题的看起来像是一 ...

  3. DEV中gridview常用属性

    1.隐藏最上面的GroupPanel: gridView1.OptionsView.ShowGroupPanel=false; 2.得到当前选定记录某字段的值: sValue=Table.Rows[g ...

  4. python 中几个层次的中文编码.md

    转自:[http://swj.me/] 介绍 一直不太喜欢使用命令行,所以去年年底的技术创新中,使用TkInter来开发小工具.结果花费了大量的时间来学习TkInter ui的使用. 最近想整理该工具 ...

  5. svn搭建(linux下)

    安装svn: 依赖包: yum install openssl openssl-devel 问题(可以忽略不计):configure: WARNING: we have configured with ...

  6. 前端 javascript 写代码方式

    javascript 和python一样可以用终端写代码 写Js代码: - html文件中编写 - 临时,浏览器的终端 console  

  7. 在firefox安装Selenium IDE

    1.打开地址:https://addons.mozilla.org/en-US/firefox/addon/selenium-ide/: 2.点击Add to Firefox: 3.打开Firefox ...

  8. linux上scp远程复制的使用

    一.实例展示 两台机器IP分别为:A.104.238.161.75,B.43.224.34.73. ------把别的服务器上的资源拿到自己的上边 在A服务器上操作,将B服务器上/home/lk/目录 ...

  9. PHP压缩与解压Zip(PHPZip类)

    <?php     class PHPZip     {         private $ctrl_dir     = array();         private $datasec    ...

  10. 怎么创建Porlet项目

    首先找到你liferay安装的地址D:\liferay\liferay-bundles-6.1.1\plugins-sdk,找到portlets这个文件夹点击(shift+鼠标右键)进入命令行窗口,写 ...