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
Just because I think the other answers pretty much go off on a tangent of whether a football team "is-a" List<FootballPlayer>
or "has-a" List<FootballPlayer>
, which really doesn't answer this question as written.
The OP chiefly asks for clarification on guidelines for inheriting from List<T>
:
A guideline says that you shouldn't inherit from
List<T>
. Why not?
Because List<T>
has no virtual methods. This is less of a problem in your own code, since you can usually switch out the implementation with relatively little pain - but can be a much bigger deal in a public API.
What is a public API and why should I care?
A public API is an interface you expose to 3rd party programmers. Think framework code. And recall that the guidelines being referenced are the ".NET Framework Design Guidelines" and not the ".NET Application Design Guidelines". There is a difference, and - generally speaking - public API design is a lot more strict.
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?
Pretty much, yeah. You may want to consider the rationale behind it to see if it applies to your situation anyway, but if you're not building a public API then you don't particularly need to worry about API concerns like versioning (of which, this is a subset).
If you add a public API in the future, you will either need to abstract out your API from your implementation (by not exposing your List<T>
directly) or violate the guidelines with the possible future pain that entails.
Why does it even matter? A list is a list. What could possibly change? What could I possibly want to change?
Depends on the context, but since we're using FootballTeam
as an example - imagine that you can't add a FootballPlayer
if it would cause the team to go over the salary cap. A possible way of adding that would be something like:
class FootballTeam : List<FootballPlayer> {
override void Add(FootballPlayer player) {
if (this.Sum(p => p.Salary) + player.Salary > SALARY_CAP)) {
throw new InvalidOperationException("Would exceed salary cap!");
}
}
}
Ah...but you can't override Add
because it's not virtual
(for performance reasons).
If you're in an application (which, basically, means that you and all of your callers are compiled together) then you can now change to using IList<T>
and fix up any compile errors:
class FootballTeam : IList<FootballPlayer> {
private List<FootballPlayer> Players { get; set; }
override void Add(FootballPlayer player) {
if (this.Players.Sum(p => p.Salary) + player.Salary > SALARY_CAP)) {
throw new InvalidOperationException("Would exceed salary cap!");
}
}
/* boiler plate for rest of IList */
}
but, if you've publically exposed to a 3rd party you just made a breaking change that will cause compile and/or runtime errors.
TL;DR - the guidelines are for public APIs. For private APIs, do what you want.
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:
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.
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.
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.
First of all, it has to do with usability. If you use inheritance, the Team
class will expose behavior (methods) that are designed purely for object manipulation. For example, AsReadOnly()
or CopyTo(obj)
methods make no sense for the team object. Instead of the AddRange(items)
method you would probably want a more descriptive AddPlayers(players)
method.
If you want to use LINQ, implementing a generic interface such as ICollection<T>
or IEnumerable<T>
would make more sense.
As mentioned, composition is the right way to go about it. Just implement a list of players as a private variable.
I think I don't agree with your generalization. A team isn't just a collection of players. A team has so much more information about it - name, emblem, collection of management/admin staff, collection of coaching crew, then collection of players. So properly, your FootballTeam class should have 3 collections and not itself be a collection; if it is to properly model the real world.
You could consider a PlayerCollection class which like the Specialized StringCollection offers some other facilities - like validation and checks before objects are added to or removed from the internal store.
Perhaps, the notion of a PlayerCollection betters suits your preferred approach?
public class PlayerCollection : Collection<Player>
{
}
And then the FootballTeam can look like this:
public class FootballTeam
{
public string Name { get; set; }
public string Location { get; set; }
public ManagementCollection Management { get; protected set; } = new ManagementCollection();
public CoachingCollection CoachingCrew { get; protected set; } = new CoachingCollection();
public PlayerCollection Players { get; protected set; } = new PlayerCollection();
}
Let me rewrite your question. so you might see the subject from a different perspective.
When I need to represent a football team, I understand that it is basically a name. Like: "The Eagles"
string team = new string();
Then later I realized teams also have players.
Why can't I just extend the string type so that it also holds a list of players?
Your point of entry into the problem is arbitrary. Try to think what does a team have (properties), not what it is.
After you do that, you could see if it shares properties with other classes. And think about inheritance.
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:
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:
This is how is your logic presented in pictures…