multiple - c# vererbung




Warum nicht von List<T> erben? (17)

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

Das ist der richtige Weg. "Needly wordy" ist eine schlechte Art, sich das anzuschauen. Es hat eine explizite Bedeutung, wenn Sie my_team.Players.Count schreiben. Sie möchten die Spieler zählen.

my_team.Count

..bedeutet nichts. Zählen was?

Ein Team ist keine Liste - es besteht aus mehr als nur einer Liste von Spielern. Ein Team besitzt Spieler, also sollten Spieler ein Teil davon sein (ein Mitglied).

Wenn Sie sich wirklich Sorgen machen, dass es zu ausführlich ist, können Sie immer Eigenschaften aus dem Team verfügbar machen:

public int PlayerCount {
    get {
        return Players.Count;
    }
}

..die wird:

my_team.PlayerCount

Dies hat jetzt eine Bedeutung und entspricht dem Gesetz von Demeter .

Sie sollten auch das Wiederverwendungsprinzip von Composite beachten . Indem Sie von List<T> erben, sagen Sie, dass ein Team eine Liste von Spielern ist und unnötige Methoden daraus herausstellt. Das ist falsch - wie Sie sagten, ist ein Team mehr als eine Liste von Spielern: Es hat einen Namen, Manager, Vorstandsmitglieder, Trainer, medizinisches Personal, Gehaltsobergrenzen, usw. Indem Sie Ihre Teamklasse eine Liste von Spielern enthalten "Ein Team hat eine Liste von Spielern", aber es kann auch andere Dinge haben.

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?


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.


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.


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.


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.


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?


Wow, dein Beitrag hat eine ganze Reihe von Fragen und Punkten. Die meisten Argumente, die Sie von Microsoft erhalten, sind genau richtig. Beginnen wir mit allem über List<T>

  • List<T> ist stark optimiert. Es wird hauptsächlich als privates Mitglied eines Objekts verwendet.
  • Microsoft hat es nicht versiegelt, weil Sie manchmal eine Klasse mit einem freundlicheren Namen erstellen möchten: class MyList<T, TX> : List<CustomObject<T, Something<TX>> { ... } . Jetzt ist es so einfach wie var list = new MyList<int, string>(); .
  • CA1002: Stellen Sie keine generischen Listen zur Verfügung : Selbst wenn Sie planen, diese App als einzigen Entwickler zu verwenden, lohnt es sich, mit guten Programmierpraktiken zu entwickeln, damit sie Ihnen und der zweiten Natur eingetrichtert werden. Sie dürfen die Liste weiterhin als IList<T> wenn Sie für einen Verbraucher eine indizierte Liste benötigen. Damit können Sie später die Implementierung in einer Klasse ändern.
  • Microsoft hat Collection<T> sehr generisch gemacht, weil es ein generisches Konzept ist ... der Name sagt alles; Es ist nur eine Sammlung. Es gibt genauere Versionen wie SortedCollection<T> , ObservableCollection<T> , ReadOnlyCollection<T> usw., von denen jede IList<T> implementiert, nicht jedoch List<T> .
  • Collection<T> können Elemente (z. B. Hinzufügen, Entfernen usw.) außer Kraft gesetzt werden, da sie virtuell sind. List<T> nicht.
  • Der letzte Teil Ihrer Frage ist genau richtig. Ein Fußballteam ist mehr als nur eine Liste von Spielern, daher sollte es eine Klasse sein, die diese Spielerliste enthält. Denken Sie über Zusammensetzung gegen Vererbung . Eine Fußballmannschaft hat eine Liste von Spielern (eine Liste), es ist keine Liste von Spielern.

Wenn ich diesen Code schreiben würde, würde die Klasse wahrscheinlich ungefähr so ​​aussehen:

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.
}

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 
}

I just wanted to add that Bertrand Meyer, the inventor of Eiffel and design by contract, would have Team inherit from List<Player> without so much as batting an eyelid.

In his book, Object-Oriented Software Construction , he discusses the implementation of a GUI system where rectangular windows can have child windows. He simply has Window inherit from both Rectangle and Tree<Window> to reuse the implementation.

However, C# is not Eiffel. The latter supports multiple inheritance and renaming of features . In C#, when you subclass, you inherit both the interface and the implemenation. You can override the implementation, but the calling conventions are copied directly from the superclass. In Eiffel, however, you can modify the names of the public methods, so you can rename Add and Remove to Hire and Fire in your Team . If an instance of Team is upcast back to List<Player> , the caller will use Add and Remove to modify it, but your virtual methods Hire and Fire will be called.


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

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.


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.


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.


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.


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.


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.


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

Vorheriger Code bedeutet: Ein Haufen Jungs von der Straße, die Fußball spielen, und sie haben zufällig einen Namen. Etwas wie:

Wie auch immer, dieser Code (aus meiner Antwort)

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

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

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

Bedeutet: das ist eine Fußballmannschaft, die Management, Spieler, Admins usw. hat. Etwas wie:

So wird Ihre Logik in Bildern dargestellt ...





design