标签云

微信群

扫码加入我们

WeChat QR Code

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


So, is your question really when to use inheritance (a Square is-a Rectangle because it is always reasonable to provide a Square when a generic Rectangle was requested) and when to use composition (a FootballTeam has-a List of FootballPlayers, in addition to other properties which are just as "fundamental" to it, like a name)? Would other programmers be confused if elsewhere in the code you passed them a FootballTeam when they were expecting a simple List?

2019年07月20日04分13秒

what if i tell you that a football team is a business company which OWNS a list of player contracts instead of a bare List of players with some additional properties?

2019年07月21日04分13秒

It's really quite simple. IS a football team a roster of players? Obviously not, because like you say, there are other relevant properties. Does a football team HAVE a roster of players? Yes, so it should contain a list. Other than that, composition is just preferable to inheritance in general, because it's easier to modify later. If you find inheritance and composition logical at the same time, go with composition.

2019年07月20日04分13秒

FlightOdyssey: Suppose you have a method SquashRectangle that takes a rectangle, halves its height, and doubles its width. Now pass in a rectangle that happens to be 4x4 but is of type rectangle, and a square that is 4x4.What are the dimensions of the object after SquashRectangle is called?For the rectangle clearly it is 2x8. If the square is 2x8 then it is no longer a square; if it is not 2x8 then the same operation on the same shape produces a different result. The conclusion is that a mutable square is not a kind of rectangle.

2019年07月20日04分13秒

Gangnus: Your statement is simply false; a real subclass is required to have functionality which is a superset of the superclass. A string is required to do everything an object can do and more.

2019年07月20日04分13秒

Although the other answers have been very helpful, I think this one addresses my concerns most directly. As for exaggerating the amount of code, you are right that it isn't that much work in the end, but I do get confused very easily when I include some code in my program without understanding why I am including it.

2019年07月20日04分13秒

Superbest: Glad I could help! You are right to listen to those doubts; if you don't understand why you're writing some code, either figure it out, or write different code that you do understand.

2019年07月20日04分13秒

Mehrdad But you seem to forget the golden rules of optimization: 1) don't do it, 2) don't do it yet, and 3) don't do it without first doing performance profiles to show what needs optimizing.

2019年07月21日04分13秒

Mehrdad: Honestly, if your application requires that you care about the performance burden of, say, virtual methods, then any modern language (C#, Java, etc) is not the language for you. I have a laptop right here that could run a simulation of every arcade game I played as a child simultaneously. This allows me to not worry about a level of indirection here and there.

2019年07月21日04分13秒

Superbest: Now we are definitely at the "composition not inheritance" end of the spectrum. A rocket is composed of rocket parts. A rocket is not "a special kind of parts list"! I would suggest to you that you trim it down further; why a list, as opposed to an IEnumerable<T> -- a sequence?

2019年07月20日04分13秒

"your post has an entire sleuth of questions" - Well, I did say I was thoroughly confused. Thanks for linking to Readonly's question, I now see that a big part of my question is a special case of the more general Composition vs. Inheritance problem.

2019年07月20日04分13秒

Brian: Except that you can't create a generic using alias, all types must be concrete. In my example, List<T> is extended as a private inner class which uses generics to simplify the usage and declaration of List<T>. If the extension was merely class FootballRoster : List<FootballPlayer> { }, then yes I could have used an alias instead. I guess it's worth noting that you could add additional members/functionality, but it is limited since you cannot override important members of List<T>.

2019年07月20日04分13秒

If this were Programmers.SE, I'd agree with the current range of answer votes, but, as it is an SO post, this answer at least attempts to answer the C# (.NET) specific issues, even though there are definite general design issues.

2019年07月21日04分13秒

Collection<T> allows for members (i.e. Add, Remove, etc.) to be overridden Now that is a good reason not to inherit from List. The others answers have way too much philosophy.

2019年07月20日04分13秒

m-y: I suspect you mean "slew" of questions, since a sleuth is a detective.

2019年07月20日04分13秒

All it needs now is a SackManager() method...

2019年07月20日04分13秒

I would expect that your second example is a Football Club, not a football team. A football club has managers and admin etc.From this source:"A football team is the collective name given to a group of players ... Such teams could be selected to play in a match against an opposing team, to represent a football club ...".So it is my opinion that a team is a list of players (or perhaps more accurately a collection of players).

2019年07月20日04分13秒

More optimised private readonly List<T> _roster = new List<T>(44);

2019年07月20日04分13秒

It's necessary to import System.Linq namespace.

2019年07月20日04分13秒

Expanding on #2, it doesn't make sense for a List to have-a List.

2019年07月20日04分13秒

First, the question has nothing to do with composition vs inheritance. The answer is OP doesn't want to implement a more specific kind of list, so he should not extend List<>. I'm twisted cause you have very high scores on stackoverflow and should know this clearly and people trust what you say, so by now a min of 55 people who upvoted and whose idea are confused believe either a way or the other is ok to build a system but clearly it's not! #no-rage

2019年07月21日04分13秒

sam He wants the behaviour of the list. He has two choices, he can extend list (inheritance) or he can include a list inside his object (composition). Perhaps you have misunderstood part of either the question or the answer rather than 55 people being wrong and you right? :)

2019年07月20日04分13秒

Yes. It's a degenerate case with only one super and sub but I'm giving the more general explanation for his specific question. He may only have one team and that may always have one list but the choice is still to inherit the list or include it as an internal object. Once you start including multiple types of team (football.Cricket. Etc) and they start being more than just a list of players you see how you have the full composition vs inheritance question. Looking at the big picture is important in cases like this to avoid wrong choices early that then mean a lot of refactoring later.

2019年07月20日04分13秒

The OP did not ask for Composition, but the use case is a clear-cut example of X:Y problem of which one of the 4 object oriented programming principles is broken, misused, or not fully understood. The more clear answer is either write a specialized collection like a Stack or Queue (which does not appear to fit the use case) or to understand composition. A Football team is NOT a List of Football players. Whether the OP asked for it explicitly or not is irrelevant, without understanding Abstraction, Encapsulation, Inheritance, and Polymorphism they will not understand the answer, ergo, X:Y amok

2019年07月20日04分13秒

Nice catch on the List versus Set. That seems to be an error only too commonly made. When there can only be one instance of an element in a collection, some sort of set, or dictionary should be a preferred candidate for the implementation. It is ok to have a team with two players with the same name. It is not ok to have the one player included twice at the same time.

2019年07月20日04分13秒

+1 for some good points, though it's more important to ask "can a data structure reasonably support everything useful (ideally short and long term)", rather than "does the data structure do more than is useful".

2019年07月20日04分13秒

TonyD Well, the points I am raising is not that one should check " if the data structure does more than what is useful". It is to check "If the Parent data structure does something that is irrelevant, meaningless or counter-intuitive to the behavior what the Child class might imply".

2019年07月21日04分13秒

SatyanRaina: and of those, there's nothing wrong with being able to do extra things that are irrelevant or meaningless as long as they don't actually compromise the operations that are meaningful and will be used.For example, if a balanced binary tree happens to iterate elements in key-sorted order and that's not necessary, it's not actually a problem either.E.g. "order of inclusion of players [being] descriptive of the state of the team": if no order is required, or the multiple orderings likely wanted can still be efficiently conveniently formed, a particular default order does no harm.

2019年07月21日04分13秒

TonyD There is actually a problem with having irrelevant characteristics derived from a Parent as it would fail the negative tests in many cases. A programmer could extend Human{ eat(); run(); write();} from a Gorilla{ eat(); run(); swing();} thinking there is nothing wrong with a human with an extra feature of being able to swing. And then in a game world your human suddenly starts to bypass all supposed land hurdles by just swinging over the trees. Unless explicitly specified, a practical Human should not be able to swing. Such a design leaves the api very open to abuse and confusing

2019年07月21日04分13秒

If the type of every player would classify it as belonging to either DefensivePlayers, OffensivePlayers, or OtherPlayers, it might be legitimately useful to have a type which could be used by code which expects a List<Player> but also included members DefensivePlayers, OffsensivePlayers, or SpecialPlayers of type IList<DefensivePlayer>, IList<OffensivePlayer>, and IList<Player>.One could use a separate object to cache the separate lists, but encapsulating them within the same object as the main list would seem cleaner [use the invalidation of a list enumerator...

2019年07月21日04分13秒

...as a cue for the fact that the main list has changed and the sub-lists will need to be regenerated when they're next accessed].

2019年07月20日04分13秒

While I agree with the point made, it really tears my soul apart to see someone give design advice and suggest exposing a concrete List<T> with a public getter and setter in a business object :(

2019年07月20日04分13秒

This doesn't explain why.The OP knows that most other programmers feel this way, he just doesn't understand why it's important.This is only really providing information already in the question.

2019年07月21日04分13秒

This is the first thing I thought of too, how his data was just modeled incorrectly. So the reason why is, your data model is incorrect.

2019年07月21日04分13秒

Why not inherit from list? Because he does not want a more specific kind of list.

2019年07月20日04分13秒

I don't think the second example is correct. You are exposing state and are thus breaking encapsulation. State should be hidden/internal to the object and the way to mutate this state should be via public methods. Exactly which methods is something to be determined from the business requirements, but it should be on a "need this right now"-basis, not "might be handy some time I dunno"-basis. Keep your api tight and you'll save yourself time and effort.

2019年07月20日04分13秒

Encapsulation is not the point of the question. I focus on not extending a type if you don't want that type to behave in a more specific way. Those fields should be autoproperties in c# and get/set methods in other languages but here in this context that is totally superfluous

2019年07月20日04分13秒

That's a good point - one could think of the team as just a name. However, if my application aims to work with the actions of the players, then that thinking is a bit less obvious. Anyway, it seems that the issue comes down to composition vs. inheritance in the end.

2019年07月21日04分13秒

That is one meritorious way to look at it.as a side note please consider this: A man of wealth owns several football teams, he gives one to a friend as a gift, the friend changes the name of the team, fires the coach, and replaces all the players. The friends team meets the men's team on the green and as the men's team is losing he says "I can't believe you are beating me with the team I gave you!" is the man correct? how would you check this?

2019年07月21日04分13秒

It might if you called it myTeam.subTeam(3, 5);

2019年07月20日04分13秒

SamLeach Assuming that's true, then you will still need composition not inheritance. As subList won't even return a Team anymore.

2019年07月20日04分13秒

OP wants to understand how to MODEL REALITY but then defines a football team as a list of football players (which is wrong conceptually). This is the problem. Any other argument is misleding to him in my opinion.

2019年07月21日04分13秒

I disagree that the list of players is wrong conceptually.Not necessarily.As someone else wrote in this page, "All models are wrong, but some are useful"

2019年07月20日04分13秒

Right models are hard to get, once done they are the ones useful. If a model can be misused it is wrong conceptually. stackoverflow.com/a/21706411/711061

2019年07月20日04分13秒

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. I don't know if I should edit this answer or post another, any idea?

2019年07月20日04分13秒

The bit about "inheriting from a List would expose the clients of this class to methods that may be should not be exposed" is precisely what makes inheriting from List an absolute no-no. Once exposed, they gradually get abused and mis-used over time. Saving time now by "developing economically" can easily lead to tenfold the savings lost in future: debugging the abuses and eventually refactoring to fix the inheritance. The YAGNI principle can also be thought of as meaning: You Ain't Gonna Need all those methods from List, so don't expose them.

2019年07月20日04分13秒

"don't over-engineer. The less code you write, the less code you will need to debug." <-- I think this is misleading. Encapsulation and composition over inheritance is NOT over-engineering and it does not cause more need for debugging. By encapsulating you are LIMITING the number of ways clients can use (and abuse) your class and thus you have fewer entry points that need testing and input validation. Inheriting from List because it's quick and easy and thus would lead to fewer bugs is plain wrong, it's just bad design and bad design leads to a lot more bugs than "over engineering".

2019年07月20日04分13秒

kai I agree with you in every point. I sincerely don't remember to what I was referring on the “don't over-engineer” comment. OTOH, I believe there are a limited number of cases where simply to inherit from List is useful. As I wrote in the later edition, it depends. The answer to each case is heavily influenced by both knowledge, experience and personal preferences. Like everything in life. ;-)

2019年07月20日04分13秒

While it is important to have the courage and initiative to challenge the accepted wisdom when appropriate, I think that it is wise to first understand why the accepted wisdom has come to be accepted in the first place, before embarking to challenge it. -1 because "Ignore that guideline!" is not a good answer to "Why does this guideline exist?" Incidentally, the community did not just "blame me" in this case, but provided a satisfactory explanation that succeeded in persuading me.

2019年07月20日04分13秒

It is actually, when no explanation is made, your only way is pushing the boundary and testing not being afraid of infraction. Now. Ask yourself, even if List<T> was not a good idea. How much a pain would it be to change inheritance from List<T> to Collection<T> ? A guess : Less time than posting on the forum, whatever the length of your code with refactoring tools. Now it is a good question, but not a practical one.

2019年07月20日04分13秒

To clarify: I'm certainly not waiting on SO's blessing to inherit from whatever I want however I want it, but the point of my question was to understand the considerations that should go into this decision, and why so many experienced programmers seem to have decided the opposite of what I did. Collection<T> is not the same as List<T> so it may require quite a bit of work to verify in a large-ish project.

2019年07月20日04分13秒

Sorry SuperBest, I did not implied your question was useless, nor that that you rely too much on community. This is a good question, or would not have bothered to give a response. My point is if someone says : "don't do that" and does not explain why, you should ignore the advice while considering the impact on what happen if you are wrong. Changing List<T> to Collection<T> is not a problem with today refactoring tools and patterns. (even for large project) As you said, List<T> have some advantage on Collection<T>, the current accepted response is a philosophical argument, not a practical one.

2019年07月21日04分13秒

team.ByName("Nicolas") means "Nicolas" is the name of the team.

2019年07月20日04分13秒

In retrospect, after the explanation of EricLippert and others, you've actually given a great answer for the API part of my question - in addition to what you said, if I do class FootballTeam : List<FootballPlayers>, users of my class will be able to tell I've inherited from List<T> by using reflection, seeing the List<T> methods that don't make sense for a football team, and being able to use FootballTeam into List<T>, so I would be revealing implementation details to the client (unnecessarily).

2019年07月21日04分13秒

Here the problem is not multiple inheritance, mixins (etc..) or how to deal with their absence. In any language, even in human language a team is not a list of people but composed of a list of people. This is an abstract concept. If Bertrand Meyer or anyone manages a team by subclassing List is doing wrong. You should subclass a List if you want a more specific kind of list. Hope you agree.

2019年07月20日04分13秒

That depends on what inheritance is to you. By itself, it is an abstract operation, it doesn't mean two classes are in an "is a special kind of" relationship, even though it is the mainstream interpretation. However, inheritance can be treated as a mechanism for implementation reuse if the language design supports it, even though composition is the pereferred alternative nowadays.

2019年07月21日04分13秒