c# >' - Pourquoi ne pas hériter de List<T>?




nécessite héritage (21)

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.

Lorsque je planifie mes programmes, je commence souvent par une chaîne de pensée comme celle-ci:

Une équipe de football est juste une liste de joueurs de football. Par conséquent, je devrais le représenter avec:

var football_team = new List<FootballPlayer>();

L'ordre de cette liste représente l'ordre dans lequel les joueurs sont listés dans la liste.

Mais je me rends compte plus tard que les équipes ont également d'autres propriétés, en plus de la simple liste des joueurs, qui doivent être enregistrées. Par exemple, le cumul des scores de cette saison, le budget actuel, les couleurs uniformes, une string représentant le nom de l'équipe, etc.

Alors je pense:

D'accord, une équipe de football est juste comme une liste de joueurs, mais en plus, elle a un nom (une string ) et un total cumulé de scores (un int ). .NET ne fournit pas de classe pour stocker les équipes de football, donc je vais faire ma propre classe. La structure existante la plus similaire et la plus pertinente est List<FootballPlayer> , j'en hériterai:

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

Mais il s'avère qu'une directive indique que vous ne devriez pas hériter de List<T> . Je suis complètement confus par cette directive à deux égards.

Pourquoi pas?

Apparemment la List est en quelque sorte optimisée pour la performance . Comment? Quels problèmes de performance vais-je causer si je prolonge la List ? Qu'est-ce qui va casser?

Une autre raison que j'ai vu est que List est fourni par Microsoft, et je n'ai aucun contrôle sur elle, donc je ne peux pas le changer plus tard, après avoir exposé une "API publique" . Mais j'ai du mal à comprendre cela. Qu'est-ce qu'une API publique et pourquoi devrais-je m'en préoccuper? Si mon projet actuel n'a pas et ne risque pas d'avoir cette API publique, puis-je ignorer en toute sécurité cette directive? Si j'hérite de List et qu'il s'avère que j'ai besoin d'une API publique, quelles difficultés aurai-je?

Pourquoi est-ce important? Une liste est une liste. Qu'est-ce qui pourrait éventuellement changer? Que pourrais-je vouloir changer?

Et enfin, si Microsoft ne voulait pas que je hérite de List , pourquoi n'a-t-il pas rendu la classe sealed ?

Quoi d'autre suis-je censé utiliser?

Apparemment, pour les collections personnalisées, Microsoft a fourni une classe Collection qui devrait être étendue au lieu de List . Mais cette classe est très nue et n'a pas beaucoup de choses utiles, comme AddRange , par exemple. La réponse de jvitor83 fournit une justification de performance pour cette méthode particulière, mais comment un AddRange lent AddRange pas meilleur que pas AddRange ?

Hériter de la Collection est beaucoup plus de travail que d'hériter de la List , et je ne vois aucun avantage. Sûrement Microsoft ne me dirait pas de faire du travail supplémentaire sans raison, alors je ne peux pas m'empêcher de penser que je suis en train de mal comprendre quelque chose, et hériter de Collection n'est pas la bonne solution pour mon problème.

J'ai vu des suggestions telles que l'implémentation d' IList . Tout simplement pas. C'est des dizaines de lignes de code standard qui ne me rapporte rien.

Enfin, certains suggèrent d'encapsuler la List dans quelque chose:

class FootballTeam 
{ 
    public List<FootballPlayer> Players; 
}

Il y a deux problèmes avec ceci:

  1. Cela rend mon code inutilement verbeux. Je dois maintenant appeler my_team.Players.Count au lieu de my_team.Count . Heureusement, avec C #, je peux définir des indexeurs pour rendre l'indexation transparente, et transmettre toutes les méthodes de la List interne ... Mais c'est beaucoup de code! Qu'est-ce que je reçois pour tout ce travail?

  2. Cela n'a tout simplement aucun sens. Une équipe de football n'a "pas" de liste de joueurs. C'est la liste des joueurs. Vous ne dites pas "John McFootballer a rejoint les joueurs de SomeTeam". Vous dites "John a rejoint SomeTeam". Vous n'ajoutez pas de lettre aux "caractères d'une chaîne", vous ajoutez une lettre à une chaîne. Vous n'ajoutez pas de livre aux livres d'une bibliothèque, vous ajoutez un livre à une bibliothèque.

Je me rends compte que ce qui se passe "sous le capot" peut être considéré comme "ajoutant X à la liste interne de Y", mais cela semble être une façon de penser très contre-intuitive sur le monde.

Ma question (résumé)

Quelle est la façon correcte de représenter une structure de données en C #, qui, "logiquement" (c'est-à-dire "à l'esprit humain") est juste une list de things avec quelques cloches et sifflets?

L'héritage de List<T> toujours inacceptable? Quand est-ce acceptable? Pourquoi pourquoi pas? Que doit prendre en compte un programmeur lorsqu'il décide de l'héritage de List<T> ou non?


Enfin, certains suggèrent d'encapsuler la liste dans quelque chose:

C'est la bonne façon. "Inutile verbeux" est une mauvaise façon de voir cela. Il a une signification explicite lorsque vous écrivez my_team.Players.Count . Vous voulez compter les joueurs.

my_team.Count

..ne signifie rien. Comptez quoi?

Une équipe n'est pas une liste - le composé de plus que juste une liste de joueurs. Une équipe possède des joueurs, donc les joueurs devraient en faire partie (un membre).

Si vous êtes vraiment inquiet à propos d'être trop verbeux, vous pouvez toujours exposer les propriétés de l'équipe:

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

..which devient:

my_team.PlayerCount

Cela a un sens maintenant et adhère à la loi de Demeter .

Vous devriez également envisager de respecter le principe de réutilisation composite . En héritant de List<T> , vous dites qu'une équipe est une liste de joueurs et qu'elle expose des méthodes inutiles. C'est faux - comme vous l'avez dit, une équipe est plus qu'une liste de joueurs: elle a un nom, des gestionnaires, des membres du conseil, des entraîneurs, du personnel médical, des plafonds salariaux, etc. disons "Une équipe a une liste de joueurs", mais elle peut aussi avoir d'autres choses.


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.


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

Le code précédent signifie: un groupe de gars de la rue jouant au football, et ils ont un nom. Quelque chose comme:

Quoi qu'il en soit, ce code (de ma réponse)

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

Moyens: ceci est une équipe de football qui a la gestion, les joueurs, les admins, etc. Quelque chose comme:

Voici comment est présentée votre logique en images ...


Comme tout le monde l'a souligné, une équipe de joueurs n'est pas une liste de joueurs. Cette erreur est faite par beaucoup de gens partout, peut-être à différents niveaux d'expertise. Souvent, le problème est subtil et parfois très grave, comme dans ce cas. De telles conceptions sont mauvaises parce qu'elles violent le principe de substitution de Liskov . L'Internet a beaucoup de bons articles expliquant ce concept par exemple, http://en.wikipedia.org/wiki/Liskov_substitution_principle

En résumé, il existe deux règles à conserver dans une relation Parent / Enfant entre les classes:

  • Un enfant ne devrait exiger aucune caractéristique moins que ce qui définit complètement le parent.
  • Un Parent ne devrait pas exiger de caractéristique en plus de ce qui définit complètement l'Enfant.

En d'autres termes, un parent est une définition nécessaire d'un enfant, et un enfant est une définition suffisante d'un parent.

Voici un moyen de réfléchir à la solution et d'appliquer le principe ci-dessus qui devrait aider à éviter une telle erreur. On devrait tester son hypothèse en vérifiant si toutes les opérations d'une classe parente sont valides pour la classe dérivée à la fois structurellement et sémantiquement.

  • Une équipe de football est-elle une liste de joueurs de football? (Est-ce que toutes les propriétés d'une liste s'appliquent à une équipe dans le même sens)
    • Une équipe est-elle une collection d'entités homogènes? Oui, l'équipe est une collection de joueurs
    • L'ordre d'inclusion des joueurs est-il descriptif de l'état de l'équipe et l'équipe s'assure-t-elle que la séquence est conservée à moins d'être explicitement modifiée? Non, et non
    • Les joueurs devraient-ils être inclus / abandonnés en fonction de leur position séquentielle dans l'équipe? Non

Comme vous le voyez, seule la première caractéristique d'une liste est applicable à une équipe. D'où une équipe n'est pas une liste. Une liste serait un détail d'implémentation de la façon dont vous gérez votre équipe, elle ne devrait donc être utilisée que pour stocker les objets joueurs et être manipulée avec des méthodes de classe Team.

À ce stade, je voudrais faire remarquer qu'une classe d'équipe ne devrait, à mon avis, même pas être mise en œuvre en utilisant une liste; il devrait être implémenté en utilisant une structure de données Set (HashSet, par exemple) dans la plupart des cas.


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; par exemple:

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.


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.


Wow, votre message a un tas de questions et de points. La plupart du raisonnement que vous obtenez de Microsoft est exactement sur le point. Commençons par tout sur List<T>

  • List<T> est hautement optimisée. Son utilisation principale est d'être utilisé comme membre privé d'un objet.
  • Microsoft ne l'a pas caché parce que parfois vous pourriez vouloir créer une classe qui a un nom plus convivial: class MyList<T, TX> : List<CustomObject<T, Something<TX>> { ... } . Maintenant, c'est aussi simple que de faire var list = new MyList<int, string>(); .
  • CA1002: N'exposez pas les listes génériques : En principe , même si vous envisagez d'utiliser cette application en tant que développeur unique, il est utile de développer de bonnes pratiques de codage, de sorte qu'elles vous inculquent à vous et une seconde nature. Vous êtes toujours autorisé à exposer la liste en tant que IList<T> si vous avez besoin d'un consommateur pour avoir une liste indexée. Cela vous permet de changer l'implémentation dans une classe plus tard.
  • Microsoft a rendu Collection<T> très générique parce que c'est un concept générique ... le nom dit tout; c'est juste une collection. Il existe des versions plus précises telles que SortedCollection<T> , ObservableCollection<T> , ReadOnlyCollection<T> , etc., qui implémentent chacune IList<T> mais pas List<T> .
  • Collection<T> permet de remplacer les membres (c.-à-d. Ajouter, Supprimer, etc.) car ils sont virtuels. List<T> ne l'est pas.
  • La dernière partie de votre question est sur place. Une équipe de football est plus que juste une liste de joueurs, donc ce devrait être une classe qui contient cette liste de joueurs. Pensez Composition vs Héritage . Une équipe de football a une liste de joueurs (une liste), ce n'est pas une liste de joueurs.

Si j'écrivais ce code, la classe ressemblerait probablement à ceci:

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 
}

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.


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.


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.


C'est un exemple classique de composition et d' inheritance .

Dans ce cas précis:

Est-ce que l'équipe a une liste de joueurs avec un comportement supplémentaire?

ou

Est-ce que l'équipe est un objet qui contient une liste de joueurs.

En étendant la liste, vous vous limitez de plusieurs façons:

  1. Vous ne pouvez pas restreindre l'accès (par exemple, empêcher les personnes de modifier la liste). Vous obtenez toutes les méthodes de liste si vous avez besoin / voulez tout ou pas.

  2. Que se passe-t-il si vous voulez avoir des listes d'autres choses aussi. Par exemple, les équipes ont des entraîneurs, des gérants, des supporteurs, de l'équipement, etc. Certaines d'entre elles pourraient bien être des listes à part entière.

  3. Vous limitez vos options d'héritage. Par exemple, vous pouvez créer un objet Team générique, puis hériter de BaseballTeam, FootballTeam, etc. Pour hériter de List, vous devez faire l'héritage de Team, mais cela signifie que tous les différents types d'équipe sont forcés d'avoir la même implémentation de cette liste.

Composition - y compris un objet donnant le comportement que vous voulez dans votre objet.

Héritage - votre objet devient une instance de l'objet qui a le comportement que vous voulez.

Les deux ont leurs utilisations, mais c'est un cas clair où la composition est préférable.


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

If your class users need all the methods and properties** List has, you should derive your class from it. If they don't need them, enclose the List and make wrappers for methods your class users actually need.

This is a strict rule, if you write a public API , or any other code that will be used by many people. You may ignore this rule if you have a tiny app and no more than 2 developers. This will save you some time.

For tiny apps, you may also consider choosing another, less strict language. Ruby, JavaScript - anything that allows you to write less code.


Il y a quelques bonnes réponses ici. Je leur ajouterais les points suivants.

Quelle est la façon correcte de représenter une structure de données en C #, qui, "logiquement" (c'est-à-dire "à l'esprit humain") est juste une liste de choses avec quelques cloches et sifflets?

Demandez à dix personnes non informaticiennes qui connaissent l'existence du football de remplir le champ:

A football team is a particular kind of _____

Est- ce que quelqu'un a dit "liste de joueurs de football avec quelques cloches et sifflets", ou ont-ils tous dit "équipe sportive" ou "club" ou "organisation"? Votre idée qu'une équipe de football est un type particulier de liste de joueurs est dans votre esprit humain et votre esprit humain seul.

List<T> est un mécanisme . L'équipe de football est un objet métier , c'est-à-dire un objet qui représente un concept appartenant au domaine métier du programme. Ne mélangez pas ceux-là! Une équipe de football est une sorte d' équipe; il a une liste, une liste est une liste de joueurs . Une liste n'est pas un type particulier de liste de joueurs . Une liste est une liste de joueurs. Donc, faites une propriété appelée Roster qui est List<Player> . Et faites-le ReadOnlyList<Player> pendant que vous y êtes, à moins que vous ne croyiez que tous ceux qui connaissent une équipe de football peuvent supprimer des joueurs de la liste.

L'héritage de List<T> toujours inacceptable?

Inacceptable à qui? Moi? Non.

Quand est-ce acceptable?

Lorsque vous construisez un mécanisme qui étend le mécanisme List<T> .

Que doit prendre en compte un programmeur lorsqu'il décide de l'héritage de List<T> ou non?

Est-ce que je construis un mécanisme ou un objet métier ?

Mais c'est beaucoup de code! Qu'est-ce que je reçois pour tout ce travail?

Vous avez passé plus de temps à dactylographier votre question qu'il vous aurait fallu écrire des méthodes de transmission pour les membres concernés de la List<T> cinquante fois. Vous n'avez clairement pas peur de la verbosité, et nous parlons d'une très petite quantité de code ici; C'est un travail de quelques minutes.

METTRE À JOUR

Je lui ai fait réfléchir et il y a une autre raison de ne pas modeler une équipe de football comme une liste de joueurs. En fait, il pourrait être une mauvaise idée de modéliser une équipe de football comme ayant une liste de joueurs aussi. Le problème avec une équipe en tant que / ayant une liste de joueurs est que ce que vous avez est un instantané de l'équipe à un moment donné . Je ne sais pas quelle est votre analyse de rentabilité pour cette classe, mais si j'avais une classe qui représentait une équipe de football, je voudrais poser des questions comme "combien de joueurs de Seahawks ont manqué des matches entre 2003 et 2013?" ou "Quel joueur de Denver qui a déjà joué pour une autre équipe a connu la plus forte augmentation d'une année sur l'autre?" ou " Les Piggers sont-ils allés jusqu'au bout cette année? "

Autrement dit, une équipe de football me semble être bien modélisée comme une collection de faits historiques , comme lorsqu'un joueur a été recruté, blessé, retiré, etc. Évidemment, la liste actuelle des joueurs est un fait important qui devrait probablement être avant et après. centre, mais il peut y avoir d'autres choses intéressantes que vous voulez faire avec cet objet qui nécessitent une perspective plus historique.


Tout d'abord, cela concerne la convivialité. Si vous utilisez l'héritage, la classe Team exposera un comportement (méthodes) conçu uniquement pour la manipulation d'objets. Par exemple, les AsReadOnly() ou CopyTo(obj) n'ont aucun sens pour l'objet d'équipe. Au lieu de la AddRange(items) , vous voudrez probablement une méthode AddPlayers(players) plus descriptive.

Si vous voulez utiliser LINQ, implémenter une interface générique telle que ICollection<T> ou IEnumerable<T> aurait plus de sens.

Comme mentionné, la composition est la bonne façon de s'y prendre. Implémentez simplement une liste de joueurs en tant que variable privée.


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. C'est tout. 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.


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.


Une autre différence importante est que Hashtable est thread safe. Hashtable a intégré la sécurité de thread multiple lecteur / écrivain unique (MR / SW) qui signifie que Hashtable permet à un écrivain avec plusieurs lecteurs sans verrouillage.

Dans le cas du dictionnaire, il n'y a pas de sécurité de thread; Si vous avez besoin d'une sécurité de thread, vous devez implémenter votre propre synchronisation.

Pour élaborer plus loin:

Hashtable fournit une certaine sécurité des threads via la propriété Synchronized , qui renvoie un wrapper thread-safe autour de la collection. Le wrapper fonctionne en verrouillant la collection entière sur chaque opération d'ajout ou de suppression. Par conséquent, chaque thread qui tente d'accéder à la collection doit attendre son tour pour prendre le verrou unique. Cela n'est pas évolutif et peut entraîner une dégradation significative des performances pour les grandes collections. De plus, la conception n'est pas complètement protégée des conditions de course.

Les classes de collection .NET Framework 2.0 telles que List<T>, Dictionary<TKey, TValue> , etc. ne fournissent aucune synchronisation de threads; le code utilisateur doit fournir toute la synchronisation lorsque des éléments sont ajoutés ou supprimés simultanément sur plusieurs threads

Si vous avez besoin de sécurité de type sécurité des threads, utilisez des classes de collections simultanées dans le .NET Framework. Plus de lecture here .

Une différence supplémentaire est que lorsque nous ajoutons les entrées multiples dans le dictionnaire, l'ordre dans lequel les entrées sont ajoutées est conservé. Lorsque nous récupérons les éléments du dictionnaire, nous obtenons les enregistrements dans le même ordre que nous les avons insérés. Alors que Hashtable ne conserve pas l'ordre d'insertion.





c# .net oop inheritance design