Why not inherit from List?

前端 未结 27 1757
悲哀的现实
悲哀的现实 2020-11-21 05:39

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

相关标签:
27条回答
  • 2020-11-21 05:54

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

    0 讨论(0)
  • 2020-11-21 05:54

    Problems with serializing

    One aspect is missing. Classes that inherit from List<> can't be serialized correctly using XmlSerializer. In that case DataContractSerializer must be used instead, or an own serializing implementation is needed.

    public class DemoList : List<Demo>
    {
        // using XmlSerializer this properties won't be seralized:
        string AnyPropertyInDerivedFromList { get; set; }     
    }
    
    public class Demo
    {
        // this properties will be seralized
        string AnyPropetyInDemo { get; set; }  
    }
    

    Further reading: When a class is inherited from List<>, XmlSerializer doesn't serialize other attributes

    0 讨论(0)
  • 2020-11-21 05:56

    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
    {
        // 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.
    }
    
    0 讨论(0)
  • 2020-11-21 05:56

    Design > Implementation

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

    An object is a collection of data and behaviour.

    So your first questions should be:

    • What data does this object comprise in the model I'm creating?
    • What behaviour does this object exhibit in that model?
    • How might this change in future?

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

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

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

    Consider Design Specifics

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

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

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

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

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

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

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

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

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

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

    Move on to Implementation

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

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

    A Thought Experiment

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

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

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

    The Short Answer (Too Late)

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

    0 讨论(0)
  • 2020-11-21 05:56

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

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

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

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

    -- Edited

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

    -- Edition 2

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

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

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

    0 讨论(0)
  • 2020-11-21 05:58

    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.

    0 讨论(0)
提交回复
热议问题