Why not inherit from List<T>?

224,250

Solution 1

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

Solution 2

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 lets 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<T>//generic class
{
    // Football team rosters are generally 53 total players.
    private readonly List<T> _roster = new List<T>(53);

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

    // Yes. I used LINQ here. This is so I don't have to worry about
    // _roster.Length vs _roster.Count vs anything else.
    public int PlayerCount
    {
        get { return _roster.Count(); }
    }

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

Solution 3

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:

People playing football

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

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

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

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

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

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

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

Image showing members (and other attributes) of the Manchester United team

This is how is your logic presented in pictures…

Solution 4

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.

Solution 5

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

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.

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.

Share:
224,250
Superbest
Author by

Superbest

Just another spaghetti chef.

Updated on July 17, 2022

Comments

  • Superbest
    Superbest almost 2 years

    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?

    • Flight Odyssey
      Flight Odyssey about 10 years
      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?
    • Superbest
      Superbest about 10 years
      @FlightOdyssey It seems that in practice, my question turns out to be "Why is composition better than inheritance when extending the functionality of List?" I would consider the "list of players" to be a most fundamental property of a football team - above all others like name or budget. A team can exist which has no name (neighborhood kids who form two teams to play on an afternoon) but a team without players does not make sense to me. As for being surprised, I don't know. I'm having trouble imagining anyone being surprised by such inheritance, but I feel I might be overlooking something.
    • Karl Knechtel
      Karl Knechtel about 10 years
    • STT LCU
      STT LCU about 10 years
      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?
    • Tobberoth
      Tobberoth about 10 years
      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.
    • Izkata
      Izkata about 10 years
      my_team.CountFootballs, my_team.CountSpareUniforms...
    • Eric Lippert
      Eric Lippert about 10 years
      @FlightOdyssey: Be careful with the a-square-is-a-kind-of-rectangle analogy; it only works if Square and Rectangle are immutable values. If they are mutable then you have problems.
    • Eric Lippert
      Eric Lippert about 10 years
      @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.
    • dlev
      dlev about 10 years
      @EricLippert Clearly, Squash() should just be a virtual method on the Rectangle class, and you get a helpful NotSupportedException when you try to call the method on a Square that is masquerading as a Rectangle. To avoid that exception, I would then add a virtual IsSquashable property to the class, thus converting the vexing exception into a boneheaded exception (since they should have just checked the flag!) Then, I... oh no, I've gone cross-eyed. OO design is hard.
    • Gangnus
      Gangnus about 10 years
      @EricLippert Thus any inheritance won't work, because a real subclass always has some functionalities closed, just because it is only a subset of the parent. Only we never realize all functions and it is not obvious. So, your thought leads to absurd, and is false. Yes, such functions that don't fit the subclass must be overridden.
    • Eric Lippert
      Eric Lippert about 10 years
      @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.
    • Flight Odyssey
      Flight Odyssey about 10 years
      @EricLippert Fair enough point regarding Rectangles and Squares. My purpose in using that particular example was not to say that in every possible class structure a Square should be implemented as a subclass of a Rectangle, just to give a simple example to illustrate the general concept of inheritance without getting too bogged down in extended explanations and/or obscure terminology.
    • dan_l
      dan_l about 10 years
      List<T> have too many methods and properties that are not relevant to your business . Perhaps you can declare football team as an interface. A list based implement is one possible implementation.
    • nawfal
      nawfal almost 10 years
      Answers here make a valid point (especially see Eric Lippert's and Sam Leach's). Indeed a football team is not a list of players. It's just one aspect of it. A class that could be inheriting from List<T> that makes sense would be if its named FootballPlayerCollection (purists would correct me that Collection<T> is a better choice, ok, but you get the point).
    • stakx - no longer contributing
      stakx - no longer contributing about 9 years
      Slightly off-topic (but still worth mentioning IMHO): List<T> is not a good fit because it can contain duplicates. Could a football team really have the same player more than once?! That would be the case if FootballTeam inherited from or contained a List<T>. I recommend that you look at a collection type with stricter guarantees, such as ISet<T>.
    • Zesty
      Zesty almost 9 years
      I disagree with the two "problems" you listed. Firstly, if I have 1 team and 11 players, I expect my_team.Players.Count = 11, Players.Count (an array) = 11 and my_team.Count = 1 (really, my_team shouldn't even have a Count method). Secondly, a football team isn't just the players - it has various properties like established on, state, etc. Thus, it is more appropriately modeled as my_team, which includes Players as a property. class FootballTeam { public List<FootballPlayer> Players; } is the natural way to model it, even without MS having to give you a guideline.
    • M. Pathan
      M. Pathan over 8 years
    • FreeText
      FreeText almost 6 years
      Wow, did the answers go off the rails here. Modelling is always tricky, but really the question is about when and when not to inherit from List, and why the MS guidelines suggest not to. Answer the question asked, please.
    • Doga Oruc
      Doga Oruc almost 3 years
      "You don't add a book to a library's books, you add a book to a library", you might want to look what "Meotnymy" is.
  • Superbest
    Superbest about 10 years
    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.
  • Eric Lippert
    Eric Lippert about 10 years
    @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.
  • Jules
    Jules about 10 years
    @EricLippert "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." -- It's been a while since I worked in Visual Studio, but when working in Java with Eclipse the environment can autogenerate these methods (Source/Generate delegate methods/select the list/finish: 4 mouse clicks). Does VS not have an option to do this?
  • peterh
    peterh about 10 years
    The difference between the so-named "mechanism" and "business objects" doesn't explain, why a List<T> shouldn't be extended to a "business object". Every object are "mechanisms", although not always so clear and simple as a such generic element.
  • peterh
    peterh about 10 years
    FootballGroup : List<FootballPlayers>, it seems absolutely acceptable and very efficient to me. There weren't any real technical reasons explained, why this construction didn't deserve the right to live, only phylosophical arguments.
  • user541686
    user541686 about 10 years
    "When is it acceptable? When you're building a mechanism that extends the List<T> mechanism."... or when you must care about the performance of that extra level of indirection, which no one seems to ever mention.
  • AJMansfield
    AJMansfield about 10 years
    @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.
  • Brian
    Brian about 10 years
    I am skeptical of your argument that List is unsealed to allow for friendlier names. That is what using aliases are for.
  • Eric Lippert
    Eric Lippert about 10 years
    @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.
  • Eric Lippert
    Eric Lippert about 10 years
    @Jules: VS has such a mechanism for implementing interfaces; I don't know offhand if it has one for forwarding methods. It would be easy to write in Roslyn if it doesn't exist.
  • Brian
    Brian about 10 years
    @Jules: I'm not confident enough in my VS skills to insist that the VS IDE lacks this feature, but will note that some VS extensions (e.g., R#) do support this.
  • myermian
    myermian about 10 years
    @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>.
  • user541686
    user541686 about 10 years
    @EricLippert: If you don't need to worry about it then you don't need to worry about it. If you do, then you do. All I was saying is that when it's a bottleneck, inheritance instead of composition can be a solution. No reason why you have to dump the entire language just because you came across a bottleneck somewhere...
  • Servy
    Servy about 10 years
    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.
  • rball
    rball about 10 years
    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.
  • WernerCD
    WernerCD about 10 years
    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. - Consider YAGNI? Sure you MIGHT at some point in the near or far future need to look at that stuff... but do you need to NOW? Building for the possibility of a future need (that may or may not ever exist...) is wasting effort.
  • Eric Lippert
    Eric Lippert about 10 years
    @WernerCD: I agree absolutely. We are not given by the original poster what the business of the program is, just that football teams are involved. If this is a simulation for a game, that's one thing. If it's intended to model reality, that's another entirely.
  • Superbest
    Superbest about 10 years
    With regard to your update: I was actually trying to make a Delta-V calculator, which consumes a list of rocket parts (with properties such as mass and fuel amount). Indeed, a rocket does not have a single list of parts, but it has different sets of parts for each stage. I've handled it by giving the rocket a List<Stage>, where each Stage has a List<RocketPart> - although I became curious about the general problem aside from this particular program.
  • Eric Lippert
    Eric Lippert about 10 years
    @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?
  • ArturoTena
    ArturoTena about 10 years
    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?
  • Fred Mitchell
    Fred Mitchell about 10 years
    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.
  • Matt Johnson-Pint
    Matt Johnson-Pint about 10 years
    @EricLippert - You should take a look at Fowler's Temporal Patterns write-up if you haven't already. :) But whether you need to do that or not is heavily dependent on business use case and storage/retrieval requirements. For example, you might want your object to represent only a snapshot, but have your database infrastructure be aware of the temporal aspect. I did exactly that in this project.
  • Eric Lippert
    Eric Lippert about 10 years
    @MattJohnson: Thanks for the link. One of the ironies of how OOP is taught is that "Bank Account" is often an early exercise given to students. And of course how they are taught to model it is the exact opposite of how real bank accounts are modeled in banking software. In real software an account is an append-only list of debits and credits from which a balance may be computed, not a mutable balance that is destroyed and replaced when an update happens.
  • Magus
    Magus about 10 years
    @Mehrdad: To reiterate what Eric Lippert said, considering everything the runtime is doing already, something will always be a worse bottleneck than a choice of composition. Anything so bizarre as to move faster purely from inheritance probably shouldn't be programmed in C#, which is definitely already affecting performance.
  • Mark Hurd
    Mark Hurd about 10 years
    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.
  • myermian
    myermian about 10 years
    @jberger: I always prefer to use the LINQ version of Enumerable.Count<TSource>(IEnumerable<TSource>) so that if I ever change the underlying type it will always work as long as it is an IEnumerable<T>. There is a very negligible performance hit vs just using List<T>.Count. One day though, I might decide I want a new data structure that implements IEnumerable<T>, but nothing else, who knows. See stackoverflow.com/a/981283/347172 for more info. :)
  • Navin
    Navin about 10 years
    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.
  • Groo
    Groo about 10 years
    @Navin: that doesn't apply for the List<T> class. Collection<T> class is supposed to be inherited in order to extend its functionality as needed.
  • myermian
    myermian about 10 years
    @Groo: You do realize that it isn't going from O(1) to O(n) if the sequence is a type of ICollection<T> or ICollection. In those cases, the LINQ function just reverts back to using the ICollection<T>.Count/ICollection.Count methods.
  • Cruncher
    Cruncher about 10 years
    Expanding on #2, it doesn't make sense for a List to have-a List.
  • Superbest
    Superbest about 10 years
    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.
  • Nicolas Dorier
    Nicolas Dorier about 10 years
    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.
  • Superbest
    Superbest about 10 years
    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.
  • Groo
    Groo about 10 years
    @m-y: Yeah, sorry, that part's correct, I didn't think that through. It also does this optimization with First, Last, and other extension methods which can benefit from accessing ICollection members directly, so there's actually not much harm in doing it.
  • Eric Lippert
    Eric Lippert about 10 years
    @MikeGraham: The point of the exercise was not to advocate for a particular methodology for creating a type hierarchy, but rather to respond to the original poster's assertion that "any human mind" would logically consider a football team to be a list of players. If you disagree with that assertion then I invite you to write an answer that you think answers the question better.
  • Nicolas Dorier
    Nicolas Dorier about 10 years
    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.
  • Mauro Sampietro
    Mauro Sampietro about 10 years
    Why not inherit from list? Because he does not want a more specific kind of list.
  • gilly3
    gilly3 about 10 years
    I totally agree with your last statement. Clearly everyone here agrees - a football team is not a list of players. The problem is that it completely misses the point of the question. It was an unfortunate example to use in the question because everyone's visceral response to it completely obscured the actual, valid question. It might have been better to say he wanted to extend List<T> for some small functionality in his Roster property. Then, we might have been able to discuss the reasons why MS recommends against extending List<T> and when it would be appropriate to do so anyway.
  • Sam Leach
    Sam Leach about 10 years
    team.ByName("Nicolas") means "Nicolas" is the name of the team.
  • Sam Leach
    Sam Leach about 10 years
    It might if you called it myTeam.subTeam(3, 5);
  • Disillusioned
    Disillusioned about 10 years
    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.
  • Superbest
    Superbest about 10 years
    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.
  • user1852503
    user1852503 about 10 years
    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?
  • Tony Delroy
    Tony Delroy about 10 years
    +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".
  • Satyan Raina
    Satyan Raina about 10 years
    @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".
  • Tony Delroy
    Tony Delroy about 10 years
    @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.
  • Satyan Raina
    Satyan Raina about 10 years
    @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
  • myermian
    myermian about 10 years
    @EricLippert: I disagree with the part of your answer that is after the update. Wouldn't it be better to create your business object so that it represents an instance of a football team, and use data warehousing techniques to get at the business intelligence/history?
  • Tony Delroy
    Tony Delroy about 10 years
    @SatyanRaina: please don't change the topic from incidental behaviours within the desirable API to fundamentally broken derivation. Specifically, your "is the order of inclusion..." and "...included/dropped based on their sequencial position..." tests are not good tests of the inappropriateness of a list, they just establish that a list has some unneeded properties. You suggest a HashSet which is sound advice, but using your logic I could say "Is it necessary to have O(1) lookup by key? No", "perhaps a performance test passes despite an inefficient algo". Testing is an arms race.
  • Eric Lippert
    Eric Lippert about 10 years
    @m-y: Better by what metric?
  • Satyan Raina
    Satyan Raina about 10 years
    @TonyD I am not suggesting that the Player class should be derived from HashSet either. I am suggesting the Player class should 'in most cases' be implemented using a HashSet via Composition and that is totally an implementation level detail, not a design level one (that is why I mentioned it as a sidenote to my answer). It could very well be implemented using a list if there is a valid justification for such an implementation. So to answer your question, Is it necessary to have O(1) lookup by key? No. Therefore one should NOT extend a Player from a HashSet also.
  • Cruncher
    Cruncher about 10 years
    @SamLeach Assuming that's true, then you will still need composition not inheritance. As subList won't even return a Team anymore.
  • Superbest
    Superbest about 10 years
    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).
  • supercat
    supercat over 9 years
    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...
  • supercat
    supercat over 9 years
    ...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].
  • Mauro Sampietro
    Mauro Sampietro over 9 years
    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.
  • xpmatteo
    xpmatteo over 9 years
    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"
  • Mauro Sampietro
    Mauro Sampietro over 9 years
    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
  • Ben
    Ben about 9 years
    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).
  • Mauro Sampietro
    Mauro Sampietro almost 9 years
    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.
  • Alexey
    Alexey almost 9 years
    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.
  • sara
    sara over 8 years
    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.
  • sara
    sara over 8 years
    "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".
  • sara
    sara over 8 years
    "don't add a constraint that should not exist" Au contraire, don't expose anything you don't have a good reason to expose. This is not purism or blind zealotry, nor is it the latest design fashion trend. It is basic object oriented design 101, beginner level. It's not a secret why this "rule" exists, it is the result of several decades of experience.
  • ArturoTena
    ArturoTena over 8 years
    @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. ;-)
  • Mauro Sampietro
    Mauro Sampietro over 8 years
    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
  • sara
    sara over 8 years
    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 :(
  • Jonathan Allen
    Jonathan Allen about 8 years
    If you write public IList<T> Roster the consumer pays a performance penalty. It is much faster to use a for-each over a List<T> than an IList<T>, like more than double on my machine.
  • myermian
    myermian about 8 years
    @JonathanAllen: That's not how the foreach keyword works (see stackoverflow.com/a/3679993/347172). In this case, the underlying type is still a List<T> which means that it will call GetEnumerator() on the type and use that enumerator to iterate over.
  • Jonathan Allen
    Jonathan Allen about 8 years
    If you call List<T>.GetEnumerator(), you get a struct named Enumerator. If you call IList<T>.GetEnumerator(), you get a variable of type IEnumerable<T> that happens to contain the boxed version of said struct. In the former case, foreach will call the methods directly. In the latter, all calls have to be virtually dispatched via the interface, making each call slower. (Roughly twice as slow on my machine.)
  • myermian
    myermian about 8 years
    @JonathanAllen: Any call to GetEnumerator() will return a type that implements IEnumerable<T> (or IEnumerable) depending on usage. The underlying type though (in this instance) is the same. It is the Enumerator struct. See the reference source: referencesource.microsoft.com/#mscorlib/system/collections/… ... they all return the same type.
  • Jonathan Allen
    Jonathan Allen about 8 years
    You are missing the point entirely. foreach doesn't care whether or not Enumerator implements the IEnumerable<T> interface. If it can find the methods it needs on Enumerator, then it won't use IEnumerable<T>. If you actually decompile the code, you can see how foreach avoids using virtual dispatch calls when iterating over List<T>, but will with IList<T>.
  • Jonathan Allen
    Jonathan Allen about 8 years
    In fact, GetEnumerator doesn't even need to return something that is IEnumerable. You could create your own enumerator that has the right methods, but doesn't implement that interface, and foreach will still work.
  • SiD
    SiD almost 8 years
    More optimised private readonly List<T> _roster = new List<T>(44);
  • Mauro Sampietro
    Mauro Sampietro almost 8 years
    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
  • Tim B
    Tim B almost 8 years
    @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? :)
  • Mauro Sampietro
    Mauro Sampietro almost 8 years
    @TimB ..are we really talking about the same thing?! en.wikipedia.org/wiki/Composition_over_inheritance
  • Tim B
    Tim B almost 8 years
    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.
  • Mr Anderson
    Mr Anderson over 7 years
    You have mixed up parent-child relationship with inheritance. Completely different.
  • Andrew Rondeau
    Andrew Rondeau over 6 years
    This is a composition vs inheritance question. The topics that the OP brought up ultimately show that, in his thinking, he doesn't really understand the difference between "is-a" and "has-a" relationships. The OP also misunderstands behaviors versus implementations.
  • Davide Cannizzo
    Davide Cannizzo over 6 years
    It's necessary to import System.Linq namespace.
  • Julia McGuigan
    Julia McGuigan over 6 years
    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
  • Tvde1
    Tvde1 almost 5 years
    If we follow Demeter's Law, it should be called team.GetPlayerByName("Nicolas") or team.GetRetiredPlayerByName("John").
  • Satyan Raina
    Satyan Raina over 4 years
    @MrAnderson Could you elaborate?
  • cmxl
    cmxl almost 4 years
    Creating an interface will prevent you from deriving from "wrong" classes in the first place, because you then exactly see what behaviour you are looking for. That was my first thought tbh.
  • E. Verdi
    E. Verdi over 3 years
    @Ben In my opinion Nean Der Thal answer is correct. A football team contains management (trainer, assistant, etc.), players (and an important note: "players selected for a match" because not all players in a team will be selected for eg. a champions league match), admins, etc. A football club is something like people at the office, the stadium, the fans, board of the club and last but not least: Multiple teams (such as the first team, the women's team, the youth teams etc.) Don't believe everything Wikipedia says ;)
  • Ben
    Ben over 3 years
    @E.Verdi I think the second picture has a stadium in the background and, as you have suggested, it makes it look like it represents a club rather than just a team. At the end of the day the definitions of these terms are just semantics (what I might call a team others might call a roster etc). I think the point is that how you model your data depends on what it is going to get used for. This is a good point and I think the pictures help to show this :)
  • Sujit Singh
    Sujit Singh almost 3 years
    There are some very interesting comments and answers on this question. If we model a football team regardless of programming language, would not it represent A team which has a name has a manager has owner have number of players player could have ranking active player team plays games team perform actions and these types of models specifies class model, data model etc. You can encapsulate some logic, actions/behaviour in relevant classes
  • Sujit Singh
    Sujit Singh almost 3 years
    So, Team will be composition of properties, methods etc where one of property could be IList<FootballPlayer>
  • JAlex
    JAlex over 2 years
    You need to specify T as a generic parameter, public class FootballTeam<T> ... otherwise the field List<T> _roster is invalid syntax.
  • Jonathan Wood
    Jonathan Wood over 2 years
    I think your use of the word mechanism here is fuzzy. I don't see one good reason not to inherit from List<> if it fits your model. And if someone can explain how it hurts the performance of the list, please do!
  • matronator
    matronator almost 2 years
    Should be pointed out with regards to your update, that you're not talking about a FootballTeam<T>, but AmericanFootballTeam<T> ;P