c# inheritance - Warum nicht von List<T>erben?



constructor multiple (21)

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

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

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

Choose one that is useful to you and/your users. Das ist es. 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.

Bei der Planung meiner Programme beginne ich oft mit einer Denkkette wie folgt:

Eine Fußballmannschaft ist nur eine Liste von Fußballspielern. Daher sollte ich es darstellen mit:

var football_team = new List<FootballPlayer>();

Die Reihenfolge dieser Liste stellt die Reihenfolge dar, in der die Spieler in der Liste aufgeführt sind.

Aber ich merke später, dass Teams neben der bloßen Liste der Spieler auch noch andere Eigenschaften haben, die aufgezeichnet werden müssen. Zum Beispiel, die laufende Summe der Ergebnisse in dieser Saison, das aktuelle Budget, die einheitlichen Farben, eine string die den Namen des Teams darstellt, etc ..

Also dann denke ich:

Okay, eine Fußballmannschaft ist wie eine Liste von Spielern, aber zusätzlich hat sie einen Namen (eine string ) und eine laufende Summe von Punkten (ein int ). .NET bietet keine Klasse zum Speichern von Fußballmannschaften an, daher werde ich meine eigene Klasse erstellen. Die ähnlichste und relevanteste existierende Struktur ist List<FootballPlayer> , daher erben wir von ihr:

class FootballTeam : List<FootballPlayer> 
{ 
    public string TeamName; 
    public int RunningTotal 
}

Aber es stellt sich heraus, dass eine Richtlinie besagt , dass Sie nicht von List<T> erben sollten . Ich bin von dieser Richtlinie in zweierlei Hinsicht völlig verwirrt.

Warum nicht?

Anscheinend ist List irgendwie für die Performance optimiert . Wie das? Welche Leistungsprobleme verursache ich, wenn ich List ? Was genau wird brechen?

Ein weiterer Grund, den ich gesehen habe, ist, dass List von Microsoft bereitgestellt wird, und ich habe keine Kontrolle darüber, also kann ich es später nicht ändern, nachdem ich eine "öffentliche API" veröffentlicht habe . Aber ich habe Mühe, das zu verstehen. Was ist eine öffentliche API und warum sollte ich mich darum kümmern? Wenn mein aktuelles Projekt diese öffentliche API nicht hat und wahrscheinlich auch nie haben wird, kann ich diese Richtlinie ignorieren? Wenn ich von List erben werde und es sich herausstellt, dass ich eine öffentliche API brauche, welche Schwierigkeiten werde ich haben?

Warum ist es überhaupt wichtig? Eine Liste ist eine Liste. Was könnte sich ändern? Was könnte ich möglicherweise ändern wollen?

Und schließlich, wenn Microsoft nicht wollte, dass ich von List erbte, warum haben sie die Klasse nicht sealed ?

Was soll ich sonst noch benutzen?

Anscheinend hat Microsoft für benutzerdefinierte Sammlungen eine Collection Klasse bereitgestellt, die anstelle von List erweitert werden sollte. Aber diese Klasse ist sehr kahl und hat nicht viele nützliche Dinge, wie zum Beispiel AddRange . Die Antwort von jvitor83 bietet eine Leistungslogik für diese bestimmte Methode, aber wie ist eine langsame AddRange nicht besser als keine AddRange ?

Das Vererben von Collection ist weitaus mehr Arbeit als das Erben von List , und ich sehe keinen Nutzen. Sicher würde Microsoft mir nicht sagen, dass ich ohne Grund zusätzliche Arbeit machen soll, also kann ich nicht umhin, mich irgendwie missverstanden zu fühlen, und das Erben von Collection ist eigentlich nicht die richtige Lösung für mein Problem.

Ich habe Vorschläge wie die Implementierung von IList . Einfach nein. Das sind Dutzende von Codezeilen, die mir nichts bringen.

Schließlich schlagen einige vor, die List in etwas zu verpacken:

class FootballTeam 
{ 
    public List<FootballPlayer> Players; 
}

Es gibt zwei Probleme damit:

  1. Es macht meinen Code unnötig ausführlich. Ich muss jetzt my_team.Players.Count anstatt nur my_team.Count . Glücklicherweise kann ich mit C # Indexer definieren, um die Indexierung transparent zu machen, und alle Methoden der internen List weiterleiten ... Aber das ist eine Menge Code! Was bekomme ich für all diese Arbeit?

  2. Es macht einfach keinen Sinn. Eine Fußballmannschaft "hat" keine Spielerliste. Es ist die Liste der Spieler. Du sagst nicht "John McFootballer hat sich SomeTeams Spielern angeschlossen". Sie sagen "John ist SomeTeam beigetreten". Sie fügen "Buchstaben einer Zeichenfolge" keinen Buchstaben hinzu, sondern fügen einer Zeichenfolge einen Buchstaben hinzu. Sie fügen den Büchern einer Bibliothek kein Buch hinzu, sondern fügen ein Buch einer Bibliothek hinzu.

Mir ist klar, dass das, was "unter der Haube" passiert, als "Hinzufügen von X zu Ys interner Liste" bezeichnet werden kann, aber dies scheint eine sehr kontraintuitive Art zu sein, über die Welt nachzudenken.

Meine Frage (zusammengefasst)

Was ist die korrekte C # -Methode, um eine Datenstruktur darzustellen, die "logisch" (das heißt "für den menschlichen Geist") nur eine list von things mit ein paar Schnickschnacks ist?

Ist das Erben von List<T> immer inakzeptabel? Wann ist es akzeptabel? Warum Warum nicht? Was muss ein Programmierer beachten, wenn er entscheidet, ob er von List<T> erbt oder nicht?


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();
}

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.


Hier gibt es einige gute Antworten. Ich würde die folgenden Punkte hinzufügen.

Was ist die korrekte C # -Methode, um eine Datenstruktur darzustellen, die "logisch" (das heißt "für den menschlichen Geist") nur eine Liste von Dingen mit ein paar Schnickschnacks ist?

Bitten Sie zehn Leute, die nicht mit dem Computerprogrammieren vertraut sind, die mit der Existenz des Fußballs vertraut sind, das leere Feld auszufüllen:

A football team is a particular kind of _____

Hat jemand "Liste der Fußballspieler mit ein paar Schnickschnack" gesagt, oder sagten alle "Sportmannschaft" oder "Verein" oder "Organisation"? Ihre Vorstellung, dass eine Fußballmannschaft eine bestimmte Art von Spielerliste ist, liegt in Ihrem menschlichen Verstand und in Ihrem menschlichen Verstand allein.

List<T> ist ein Mechanismus . Football-Team ist ein Business-Objekt - das heißt, ein Objekt, das ein Konzept darstellt, das in der Geschäftsdomäne des Programms liegt. Mischen Sie diese nicht! Eine Fußballmannschaft ist eine Art Team. Es hat einen Dienstplan, ein Dienstplan ist eine Liste von Spielern . Ein Dienstplan ist keine bestimmte Art von Spielerliste . Ein Dienstplan ist eine Liste von Spielern. Erstellen Sie eine Eigenschaft namens Roster , die eine List<Player> . Und machen Sie es ReadOnlyList<Player> wenn Sie gerade dabei sind, es sei denn, Sie glauben, dass jeder, der über eine Fußballmannschaft Bescheid weiß, Spieler aus dem Kader löschen kann.

Ist das Erben von List<T> immer inakzeptabel?

Inakzeptabel für wen? Mich? Nein.

Wann ist es akzeptabel?

Wenn Sie einen Mechanismus erstellen, der den List<T> -Mechanismus erweitert .

Was muss ein Programmierer beachten, wenn er entscheidet, ob er von List<T> erbt oder nicht?

Baue ich einen Mechanismus oder ein Geschäftsobjekt ?

Aber das ist viel Code! Was bekomme ich für all diese Arbeit?

Sie haben mehr Zeit damit verbracht, Ihre Frage einzutippen, als ob Sie die Weiterleitungsmethoden für die relevanten Mitglieder von List<T> fünfzigmal geschrieben hätten. Sie haben eindeutig keine Angst vor Ausführlichkeit, und wir sprechen hier über eine sehr kleine Menge an Code; Das ist ein paar Minuten Arbeit.

AKTUALISIEREN

Ich habe darüber nachgedacht und es gibt einen weiteren Grund, eine Fußballmannschaft nicht als Liste von Spielern zu modellieren. In der Tat könnte es eine schlechte Idee sein, eine Fußballmannschaft so zu modellieren, dass sie auch eine Liste von Spielern hat. Das Problem mit einer Mannschaft als / einer Liste von Spielern ist, dass das, was du hast, eine Momentaufnahme des Teams ist. Ich weiß nicht, was Ihr Geschäftsfall für diesen Kurs ist, aber wenn ich eine Klasse hätte, die eine Football-Mannschaft repräsentiert, würde ich gerne Fragen stellen wie "Wie viele Seahawks-Spieler haben Spiele aufgrund einer Verletzung zwischen 2003 und 2013 verpasst?" oder "Welcher Denver-Spieler, der zuvor für ein anderes Team gespielt hat, hatte den größten Anstieg der Yards im Jahresvergleich?" oder " Sind die Piggers dieses Jahr den ganzen Weg gegangen? "

Das heißt, eine Fußballmannschaft scheint mir als eine Sammlung historischer Fakten gut modelliert zu sein, wie zum Beispiel wenn ein Spieler rekrutiert, verletzt, pensioniert usw. wurde. Offensichtlich ist die aktuelle Spielerliste eine wichtige Tatsache, die wahrscheinlich Front- und Zentrum, aber es könnte andere interessante Dinge geben, die Sie mit diesem Objekt machen möchten, die eine mehr historische Perspektive erfordern.


There are a lot excellent answers here, but I want to touch on something I didn't see mentioned: Object oriented design is about empowering objects .

You want to encapsulate all your rules, additional work and internal details inside an appropriate object. In this way other objects interacting with this one don't have to worry about it all. In fact, you want to go a step further and actively prevent other objects from bypassing these internals.

When you inherit from List , all other objects can see you as a List. They have direct access to the methods for adding and removing players. And you'll have lost your control; beispielsweise:

Suppose you want to differentiate when a player leaves by knowing whether they retired, resigned or were fired. You could implement a RemovePlayer method that takes an appropriate input enum. However, by inheriting from List , you would be unable to prevent direct access to Remove , RemoveAll and even Clear . As a result, you've actually disempowered your FootballTeam class.

Additional thoughts on encapsulation... You raised the following concern:

It makes my code needlessly verbose. I must now call my_team.Players.Count instead of just my_team.Count.

You're correct, that would be needlessly verbose for all clients to use you team. However, that problem is very small in comparison to the fact that you've exposed List Players to all and sundry so they can fiddle with your team without your consent.

You go on to say:

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're wrong about the first bit: Drop the word 'list', and it's actually obvious that a team does have players.
However, you hit the nail on the head with the second. You don't want clients calling ateam.Players.Add(...) . You do want them calling ateam.AddPlayer(...) . And your implemention would (possibly amongst other things) call Players.Add(...) internally.

Hopefully you can see how important encapsulation is to the objective of empowering your objects. You want to allow each class to do its job well without fear of interference from other objects.


Dies ist ein klassisches Beispiel für composition gegen inheritance .

In diesem speziellen Fall:

Ist das Team eine Liste von Spielern mit zusätzlichem Verhalten?

oder

Ist das Team ein eigenes Objekt, das zufällig eine Liste von Spielern enthält?

Indem Sie List erweitern, beschränken Sie sich auf verschiedene Arten:

  1. Sie können den Zugriff nicht einschränken (z. B. können Sie verhindern, dass Personen den Dienstplan ändern). Sie erhalten alle List-Methoden, ob Sie sie alle benötigen / wollen oder nicht.

  2. Was passiert, wenn Sie auch andere Listen haben wollen? Zum Beispiel haben Mannschaften Trainer, Manager, Fans, Ausrüstung, etc. Einige von denen können durchaus eigene Listen sein.

  3. Sie beschränken Ihre Optionen für die Vererbung. Zum Beispiel möchten Sie vielleicht ein generisches Team-Objekt erstellen und dann BaseballTeam, FootballTeam usw. haben, die davon erben. Um von List zu erben, müssen Sie die Vererbung von Team durchführen, aber das bedeutet dann, dass alle verschiedenen Arten von Teams gezwungen sind, dieselbe Implementierung dieses Rosters zu haben.

Komposition - einschließlich eines Objekts, das das gewünschte Verhalten in Ihrem Objekt angibt.

Vererbung - Ihr Objekt wird zu einer Instanz des Objekts mit dem von Ihnen gewünschten Verhalten.

Beide haben ihren Nutzen, aber dies ist ein klarer Fall, in dem die Zusammensetzung vorzuziehen ist.


A football team is not a list of football players. A football team is composed of a list of football players!

This is logically wrong:

class FootballTeam : List<FootballPlayer> 
{ 
    public string TeamName; 
    public int RunningTotal 
}

and this is correct:

class FootballTeam 
{ 
    public List<FootballPlayer> players
    public string TeamName; 
    public int RunningTotal 
}

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.


My dirty secret: I don't care what people say, and I do it. .NET Framework is spread with "XxxxCollection" (UIElementCollection for top of my head example).

So what stops me saying:

team.Players.ByName("Nicolas")

When I find it better than

team.ByName("Nicolas")

Moreover, my PlayerCollection might be used by other class, like "Club" without any code duplication.

club.Players.ByName("Nicolas")

Best practices of yesterday, might not be the one of tomorrow. There is no reason behind most best practices, most are only wide agreement among the community. Instead of asking the community if it will blame you when you do that ask yourself, what is more readable and maintainable?

team.Players.ByName("Nicolas") 

oder

team.ByName("Nicolas")

Ja wirklich. Do you have any doubt? Now maybe you need to play with other technical constraints that prevent you to use List<T> in your real use case. But don't add a constraint that should not exist. If Microsoft did not document the why, then it is surely a "best practice" coming from nowhere.


It depends on the context

When you consider your team as a list of players, you are projecting the "idea" of a foot ball team down to one aspect: You reduce the "team" to the people you see on the field. This projection is only correct in a certain context. In a different context, this might be completely wrong. Imagine you want to become a sponsor of the team. So you have to talk to the managers of the team. In this context the team is projected to the list of its managers. And these two lists usually don't overlap very much. Other contexts are the current versus the former players, etc.

Unclear semantics

So the problem with considering a team as a list of its players is that its semantic depends on the context and that it cannot be extended when the context changes. Additionally it is hard to express, which context you are using.

Classes are extensible

When you using a class with only one member (eg IList activePlayers ), you can use the name of the member (and additionally its comment) to make the context clear. When there are additional contexts, you just add an additional member.

Classes are more complex

In some cases it might be overkill to create a extra class. Each class definition must be loaded through the classloader and will be cached by the virtual machine. This costs you runtime performance and memory. When you have a very specific context it might be OK to consider a football team as a list of players. But in this case, you should really just use a IList , not a class derived from it.

Conclusion / Considerations

When you have a very specific context, it is OK to consider a team as a list of players. For example inside a method it is completely OK to write

IList<Player> footballTeam = ...

When using F#, it can even be OK to create a type abbreviation

type FootballTeam = IList<Player>

But when the context is broader or even unclear, you should not do this. This is especially the case, when you create a new class, where it is not clear in which context it may be used in the future. A warning sign is when you start to add additional attributes to your class (name of the team, coach, etc.). This is a clear sign that the context where the class will be used is not fixed and will change in the future. In this case you cannot consider the team as a list of players, but you should model the list of the (currently active, not injured, etc.) players as an attribute of the team.


This reminds me of the "Is a" versus "has a" tradeoff. Sometimes it is easier and makesmore sense to inherit directly from a super class. Other times it makes more sense to create a standalone class and include the class you would have inherited from as a member variable. You can still access the functionality of the class but are not bound to the interface or any other constraints that might come from inheriting from the class.

Which do you do? As with a lot of things...it depends on the context. The guide I would use is that in order to inherit from another class there truly should be an "is a" relationship. So if you a writing a class called BMW, it could inherit from Car because a BMW truly is a car. A Horse class can inherit from the Mammal class because a horse actually is a mammal in real life and any Mammal functionality should be relevant to Horse. But can you say that a team is a list? From what I can tell, it does not seem like a Team really "is a" List. So in this case, I would have a List as a member variable.


Wie alle darauf hingewiesen haben, ist ein Spielerteam keine Liste von Spielern. Dieser Fehler wird von vielen Menschen überall gemacht, vielleicht auf verschiedenen Ebenen des Fachwissens. Oft ist das Problem subtil und gelegentlich sehr eklatant, wie in diesem Fall. Solche Designs sind schlecht, weil sie gegen das Liskow-Substitutionsprinzip verstoßen. Das Internet hat viele gute Artikel, die dieses Konzept erklären, zB http://en.wikipedia.org/wiki/Liskov_substitution_principle

Zusammenfassend gibt es zwei Regeln, die in einer Parent / Child-Beziehung zwischen Klassen beibehalten werden sollen:

  • Ein Kind sollte kein Merkmal benötigen, das weniger als das definiert, was das Elternteil vollständig definiert.
  • Ein Elternteil sollte zusätzlich zu dem, was das Kind vollständig definiert, kein Merkmal benötigen.

Mit anderen Worten, ein Elternteil ist eine notwendige Definition eines Kindes, und ein Kind ist eine ausreichende Definition eines Elternteils.

Hier ist ein Weg, seine Lösung zu durchdenken und das oben genannte Prinzip anzuwenden, das einen solchen Fehler vermeiden helfen sollte. Man sollte diese Hypothese testen, indem man überprüft, ob alle Operationen einer Elternklasse für die abgeleitete Klasse sowohl strukturell als auch semantisch gültig sind.

  • Ist eine Fußballmannschaft eine Liste von Fußballspielern? (Entsprechen alle Eigenschaften einer Liste einem Team in der gleichen Bedeutung)
    • Ist ein Team eine Sammlung homogener Entitäten? Ja, Team ist eine Sammlung von Spielern
    • Ist die Reihenfolge der Aufnahme von Spielern beschreibend für den Status des Teams und stellt das Team sicher, dass die Sequenz beibehalten wird, wenn sie nicht explizit geändert wird? Nein, und Nein
    • Wird erwartet, dass die Spieler aufgrund ihrer sequenziellen Position im Team berücksichtigt werden? Nein

Wie Sie sehen, ist nur das erste Merkmal einer Liste auf ein Team anwendbar. Daher ist ein Team keine Liste. Eine Liste wäre ein Implementierungsdetail, wie Sie Ihr Team verwalten, daher sollte es nur dazu verwendet werden, die Spielerobjekte zu speichern und mit Methoden der Teamklasse manipuliert zu werden.

An dieser Stelle möchte ich bemerken, dass eine Team-Klasse meiner Meinung nach nicht einmal mit einer Liste implementiert werden sollte; Es sollte in den meisten Fällen mit einer Set-Datenstruktur (z. B. HashSet) implementiert werden.


While I don't have a complex comparison as most of these answers do, I would like to share my method for handling this situation. By extending IEnumerable<T> , you can allow your Team class to support Linq query extensions, without publicly exposing all the methods and properties of List<T> .

class Team : IEnumerable<Player>
{
    private readonly List<Player> playerList;

    public Team()
    {
        playerList = new List<Player>();
    }

    public Enumerator GetEnumerator()
    {
        return playerList.GetEnumerator();
    }

    ...
}

class Player
{
    ...
}

Does allowing people to say

myTeam.subList(3, 5);

make any sense at all? If not then it shouldn't be a List.


Design> Implementierung

Welche Methoden und Eigenschaften Sie bereitstellen, ist eine Designentscheidung. Welche Basisklasse Sie erben, ist ein Implementierungsdetail. Ich denke, es lohnt sich, einen Schritt zurück zu Ersterem zu machen.

Ein Objekt ist eine Sammlung von Daten und Verhalten.

Ihre ersten Fragen sollten also lauten:

  • Welche Daten umfasst dieses Objekt in dem Modell, das ich erstelle?
  • Welches Verhalten zeigt dieses Objekt in diesem Modell?
  • Wie könnte sich das in Zukunft ändern?

Denken Sie daran, dass Vererbung eine "isa" (ist ein) Beziehung impliziert, während Zusammensetzung eine "hat eine" (hasa) Beziehung impliziert. Wählen Sie aus Ihrer Sicht den für Ihre Situation richtigen aus und berücksichtigen Sie dabei, wo sich die Dinge entwickeln könnten, wenn sich Ihre Anwendung weiterentwickelt.

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.


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> . Warum nicht?

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.


Vor allem geht es um Usability. Wenn Sie Vererbung verwenden, zeigt die Team Klasse Verhalten (Methoden) an, die ausschließlich für die Objektbearbeitung vorgesehen sind. Zum Beispiel sind die AsReadOnly() oder CopyTo(obj) für das Team-Objekt nicht sinnvoll. Anstelle der AddRange(items) -Methode möchten Sie wahrscheinlich eine AddPlayers(players) -Methode.

Wenn Sie LINQ verwenden möchten, ist die Implementierung einer generischen Schnittstelle wie ICollection<T> oder IEnumerable<T> sinnvoller.

Wie gesagt, die Komposition ist der richtige Weg. Implementiere einfach eine Liste von Spielern als private Variable.


It depends on the behaviour of your "team" object. If it behaves just like a collection, it might be OK to represent it first with a plain List. Then you might start to notice that you keep duplicating code that iterates on the list; at this point you have the option of creating a FootballTeam object that wraps the list of players. The FootballTeam class becomes the home for all the code that iterates on the list of players.

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?

Encapsulation. Your clients need not know what goes on inside of FootballTeam. For all your clients know, it might be implemented by looking the list of players up in a database. They don't need to know, and this improves your design.

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.

Exactly :) you will say footballTeam.Add(john), not footballTeam.List.Add(john). The internal list will not be visible.


Was, wenn das FootballTeam ein Reserven-Team zusammen mit dem Hauptteam hat?

class FootballTeam
{
    List<FootballPlayer> Players { get; set; }
    List<FootballPlayer> ReservePlayers { get; set; }
}

Wie würdest du das modellieren?

class FootballTeam : List<FootballPlayer> 
{ 
    public string TeamName; 
    public int RunningTotal 
}

Die Beziehung ist eindeutig a und nicht a .

oder RetiredPlayers ?

class FootballTeam
{
    List<FootballPlayer> Players { get; set; }
    List<FootballPlayer> ReservePlayers { get; set; }
    List<FootballPlayer> RetiredPlayers { get; set; }
}

Als Faustregel gilt: Wenn Sie jemals von einer Sammlung erben möchten, benennen Sie die Klasse " SomethingCollection .

Macht Ihre SomethingCollection semantisch Sinn? Tun Sie dies nur, wenn Ihr Typ eine Sammlung von Something .

Im Fall von FootballTeam klingt das nicht richtig. Ein Team ist mehr als eine Collection . Ein Team kann Trainer, Trainer usw. haben, wie die anderen Antworten darauf hingewiesen haben.

FootballCollection klingt wie eine Sammlung von Fußbällen oder eine Sammlung von Fußballutensilien. TeamCollection , eine Sammlung von Teams.

FootballPlayerCollection klingt wie eine Sammlung von Spielern, die ein gültiger Name für eine Klasse wäre, die von List<FootballPlayer> erbt, wenn Sie das wirklich tun wollten.

Wirklich List<FootballPlayer> ist eine perfekte Art, mit der man umgehen muss. Vielleicht IList<FootballPlayer> wenn du es von einer Methode IList<FootballPlayer> .

Zusammenfassend

Frag dich selbst

  1. Ist X ein Y ? oder Hat X ein Y ?

  2. Bedeutet mein Klassenname was sie sind?


When they say List<T> is "optimized" I think they want to mean that it doesn't have features like virtual methods which are bit more expensive. So the problem is that once you expose List<T> in your public API , you loose ability to enforce business rules or customize its functionality later. But if you are using this inherited class as internal within your project (as opposed to potentially exposed to thousands of your customers/partners/other teams as API) then it may be OK if it saves your time and it is the functionality you want to duplicate. The advantage of inheriting from List<T> is that you eliminate lot of dumb wrapper code that is just never going to be customized in foreseeable future. Also if you want your class to explicitly have exact same semantics as List<T> for the life of your APIs then also it may be OK.

I often see lot of people doing tons of extra work just because of FxCop rule says so or someone's blog says it's a "bad" practice. Many times, this turns code in to design pattern palooza weirdness. As with lot of guideline, treat it as guideline that can have exceptions.


In praktischer Hinsicht: Wenn Sie in Ihrer abgeleiteten Klasse keine Datenelemente haben, haben Sie keine Probleme, auch nicht in polymorpher Verwendung. Sie benötigen nur einen virtuellen Destruktor, wenn die Größen der Basisklasse und der abgeleiteten Klasse unterschiedlich sind und / oder Sie virtuelle Funktionen haben (was eine V-Tabelle bedeutet).

ABER in der Theorie: Aus [expr.delete] in der C ++ 0x FCD: Wenn in der ersten Alternative (delete object) der statische Typ des zu löschenden Objekts sich von seinem dynamischen Typ unterscheidet, sollte der statische Typ a sein Basisklasse des dynamischen Typs des zu löschenden Objekts und der statische Typ muss einen virtuellen Destruktor haben oder das Verhalten ist nicht definiert.

Aber Sie können privat von std :: vector ohne Probleme ableiten. Ich habe das folgende Muster verwendet:

class PointVector : private std::vector<PointType>
{
    typedef std::vector<PointType> Vector;
    ...
    using Vector::at;
    using Vector::clear;
    using Vector::iterator;
    using Vector::const_iterator;
    using Vector::begin;
    using Vector::end;
    using Vector::cbegin;
    using Vector::cend;
    using Vector::crbegin;
    using Vector::crend;
    using Vector::empty;
    using Vector::size;
    using Vector::reserve;
    using Vector::operator[];
    using Vector::assign;
    using Vector::insert;
    using Vector::erase;
    using Vector::front;
    using Vector::back;
    using Vector::push_back;
    using Vector::pop_back;
    using Vector::resize;
    ...




c# .net oop inheritance design