Perché non ereditare dalla lista ?

Quando pianifico i miei programmi, spesso inizio con una catena di pensieri come questa:

Una squadra di calcio è solo una lista di giocatori di calcio. Pertanto, dovrei rappresentarlo con:

var football_team = new List(); 

L’ordine di questa lista rappresenta l’ordine in cui i giocatori sono elencati nel roster.

Ma mi rendo conto più tardi che le squadre hanno anche altre proprietà, oltre alla semplice lista di giocatori, che devono essere registrate. Ad esempio, il totale parziale dei punteggi in questa stagione, il budget corrente, i colors uniformi, una string rappresenta il nome della squadra, ecc.

Allora penso:

Ok, una squadra di football è come una lista di giocatori, ma in aggiunta ha un nome (una string ) e un totale parziale di punteggi (un int ). .NET non fornisce una class per la memorizzazione delle squadre di calcio, quindi farò la mia class. La struttura esistente più simile e rilevante è List , quindi erediterò da essa:

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

Ma si scopre che una linea guida dice che non si dovrebbe ereditare dalla List . Sono completamente confuso da questa linea guida sotto due aspetti.

Perchè no?

Apparentemente List è in qualche modo ottimizzato per le prestazioni . Come mai? Quali problemi di prestazioni dovrei causare se estendo List ? Cosa si romperà esattamente?

Un’altra ragione che ho visto è che List è fornito da Microsoft e non ho alcun controllo su di esso, quindi non posso cambiarlo più tardi, dopo aver esposto una “API pubblica” . Ma faccio fatica a capirlo. Che cos’è un’API pubblica e perché dovrei preoccuparmi? Se il mio progetto attuale non ha e non è probabile che abbia mai questa API pubblica, posso ignorare tranquillamente questa linea guida? Se eredito da List e risulta che ho bisogno di un’API pubblica, quali difficoltà dovrò avere?

Perché è ancora importante? Una lista è una lista. Cosa potrebbe cambiare? Cosa potrei voler cambiare?

E infine, se Microsoft non voleva che ereditassi da List , perché non hanno sealed la class?

Cos’altro dovrei usare?

Apparentemente, per le collezioni personalizzate, Microsoft ha fornito una class Collection che dovrebbe essere estesa anziché List . Ma questa class è molto semplice e non ha molte cose utili, come ad esempio AddRange . La risposta di jvitor83 fornisce un razionale di prestazioni per quel particolare metodo, ma come è un AddRange lento non migliore di nessun AddRange ?

Ereditare dalla Collection è molto più lavoro che ereditare dalla List , e non vedo alcun beneficio. Sicuramente Microsoft non mi direbbe di fare del lavoro extra senza motivo, quindi non posso fare a meno di sentirmi in qualche modo frainteso e l’ereditare Collection non è la soluzione giusta per il mio problema.

Ho visto suggerimenti come l’implementazione di IList . Solo no. Sono dozzine di righe di codice boilerplate che non mi fanno guadagnare nulla.

Infine, alcuni suggeriscono di avvolgere la List in qualcosa:

 class FootballTeam { public List Players; } 

Ci sono due problemi con questo:

  1. Rende il mio codice inutilmente dettagliato. Ora devo chiamare my_team.Players.Count anziché solo my_team.Count . Fortunatamente, con C # posso definire gli indicizzatori per rendere l’indicizzazione trasparente e inoltrare tutti i metodi List interno … Ma questo è un sacco di codice! Cosa ottengo per tutto ciò che funziona?

  2. Semplicemente non ha alcun senso. Una squadra di calcio non “ha” una lista di giocatori. È la lista dei giocatori. Non dici “John McFootballer è entrato nei giocatori di SomeTeam”. Dici “John si è unito a SomeTeam”. Non si aggiunge una lettera a “caratteri di una stringa”, si aggiunge una lettera a una stringa. Non aggiungi un libro ai libri di una biblioteca, aggiungi un libro a una biblioteca.

Mi rendo conto che ciò che accade “sotto il cofano” si può dire che è “aggiungere la lista interna di X a Y”, ma questo sembra un modo molto controintuitivo di pensare al mondo.

La mia domanda (riassunta)

Qual è il modo corretto C # di rappresentare una struttura dati, che, “logicamente” (vale a dire, “per la mente umana”) è solo una list di things con poche campane e fischietti?

L’ereditarietà di List sempre inaccettabile? Quando è accettabile? Perché perché no? Cosa deve considerare un programmatore quando decide se ereditare dall’elenco List o no?

Ci sono alcune buone risposte qui. Vorrei aggiungere loro i seguenti punti.

Qual è il modo corretto C # di rappresentare una struttura dati, che, “logicamente” (vale a dire, “per la mente umana”) è solo una lista di cose con poche campane e fischietti?

Chiedi a una decina di persone non programmatrici di computer che hanno familiarità con l’esistenza del calcio per riempire lo spazio vuoto:

 A football team is a particular kind of _____ 

Qualcuno ha detto “elenco di giocatori di calcio con alcune campane e fischietti”, o tutti hanno detto “squadra sportiva” o “club” o “organizzazione”? La tua idea che una squadra di calcio sia un particolare tipo di elenco di giocatori è nella tua mente umana e nella tua mente umana da sola.

List è un meccanismo . La squadra di calcio è un object di business , ovvero un object che rappresenta un concetto che si trova nel dominio aziendale del programma. Non mescolare quelli! Una squadra di calcio è una specie di squadra; ha una lista, una lista è una lista di giocatori . Un elenco non è un tipo particolare di elenco di giocatori . Un elenco è un elenco di giocatori. Quindi crea una proprietà chiamata Roster che è una List . E rendi ReadOnlyList mentre ci sei, a meno che tu non creda che tutti quelli che conoscono una squadra di calcio possano eliminare giocatori dal roster.

L’ereditarietà di List sempre inaccettabile?

Inaccettabile per chi? Me? No.

Quando è accettabile?

Quando stai costruendo un meccanismo che estende il meccanismo List .

Cosa deve considerare un programmatore quando decide se ereditare dall’elenco List o no?

Sto costruendo un meccanismo o un object aziendale ?

Ma questo è un sacco di codice! Cosa ottengo per tutto ciò che funziona?

Hai passato più tempo a scrivere la tua domanda che ti avrebbe portato a scrivere metodi di inoltro per i membri rilevanti della List cinquanta volte. Ovviamente non hai paura della verbosità, e qui stiamo parlando di una quantità molto piccola di codice; questo è un paio di minuti di lavoro.

AGGIORNARE

Ci ho pensato un po ‘e c’è un altro motivo per non modellare una squadra di calcio come una lista di giocatori. In realtà potrebbe essere una ctriggers idea modellare una squadra di calcio come una lista di giocatori. Il problema con una squadra come / avere una lista di giocatori è che quello che hai è un’istantanea della squadra in un momento . Non so quale sia il tuo business case per questa class, ma se avessi una class che rappresentasse una squadra di calcio vorrei chiedergli domande come “quanti giocatori di Seahawks hanno saltato i giochi a causa di un infortunio tra il 2003 e il 2013?” o “Quale giocatore di Denver che in precedenza ha giocato per un’altra squadra ha avuto il maggiore aumento anno su anno dei cantieri?” o ” I Piggers sono andati fino in fondo quest’anno? ”

Cioè, una squadra di calcio mi sembra ben modellata come una raccolta di fatti storici come quando un giocatore è stato reclutato, ferito, in pensione, ecc. Ovviamente l’attuale lista dei giocatori è un fatto importante che dovrebbe probabilmente essere frontale e centro, ma potrebbero esserci altre cose interessanti che vuoi fare con questo object che richiedono una prospettiva più storica.

Infine, alcuni suggeriscono di avvolgere la lista in qualcosa:

Questo è il modo corretto. “Stranamente verboso” è un brutto modo di guardare a questo. Ha un significato esplicito quando scrivi my_team.Players.Count . Vuoi contare i giocatori.

 my_team.Count 

..non significa niente. Conta cosa?

Una squadra non è una lista – il consistere di più di un semplice elenco di giocatori. Una squadra possiede giocatori, quindi i giocatori dovrebbero farne parte (un membro).

Se sei davvero preoccupato che sia eccessivamente prolisso, puoi sempre esporre le proprietà del team:

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

..che diventa:

 my_team.PlayerCount 

Questo ha un significato ora e aderisce a The Law Of Demeter .

Dovresti anche considerare di aderire al principio del riutilizzo composito . Tramite l’ereditarietà di List , stai dicendo che una squadra è una lista di giocatori e che ne espone metodi inutili. Questo non è corretto – come hai affermato, una squadra è più di una lista di giocatori: ha un nome, manager, membri del consiglio di amministrazione, allenatori, personale medico, stipendi, ecc. Facendo in modo che la tua class della squadra contenga un elenco di giocatori, Stai dicendo “Una squadra ha una lista di giocatori”, ma può anche avere altre cose.

Wow, il tuo post ha un’intera serie di domande e punti. La maggior parte del ragionamento che ottieni da Microsoft è esattamente al punto. Iniziamo con tutto ciò che riguarda List

  • List è altamente ottimizzato. Il suo uso principale è quello di essere usato come membro privato di un object.
  • Microsoft non lo ha sigillato perché a volte potresti voler creare una class con un nome più amichevole: class MyList : List> { ... } . Ora è facile come var list = new MyList(); .
  • CA1002: Non esporre elenchi generici : in sostanza, anche se si prevede di utilizzare questa app come unico sviluppatore, vale la pena sviluppare con buone pratiche di codifica, in modo che si instillino in te e in una seconda natura. Sei ancora autorizzato a esporre l’elenco come un IList se hai bisogno di un consumatore per avere un elenco indicizzato. Questo ti consente di modificare l’implementazione all’interno di una class in seguito.
  • Microsoft ha reso Collection molto generica perché è un concetto generico … il nome dice tutto; è solo una collezione. Esistono versioni più precise come SortedCollection , ObservableCollection , ReadOnlyCollection , ecc. Ognuna delle quali implementa IList ma non List .
  • Collection consente ai membri (ad esempio Aggiungi, Rimuovi, ecc.) Di essere sovrascritti perché sono virtuali. List no.
  • L’ultima parte della tua domanda è azzeccata. Una squadra di calcio è più di una semplice lista di giocatori, quindi dovrebbe essere una class che contiene quella lista di giocatori. Pensa Composizione vs Eredità . Una squadra di calcio ha una lista di giocatori (un roster), non è un elenco di giocatori.

Se stavo scrivendo questo codice la class sarebbe probabilmente simile a questo:

 public class FootballTeam { // Football team rosters are generally 53 total players. private readonly List _roster = new List(53); public IList 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. } 
 class FootballTeam : List { public string TeamName; public int RunningTotal; } 

Codice precedente significa: un gruppo di ragazzi della strada che giocano a calcio, e capita di avere un nome. Qualcosa di simile a:

Gente che gioca a calcio

Ad ogni modo, questo codice (dalla mia risposta)

 public class FootballTeam { // Football team rosters are generally 53 total players. private readonly List _roster = new List(53); public IList Roster { get { return _roster; } } public int PlayerCount { get { return _roster.Count(); } } // Any additional members you want to expose/wrap. } 

Mezzi: questa è una squadra di calcio che ha dirigenti, giocatori, amministratori, ecc. Qualcosa del tipo:

Immagine che mostra membri (e altri attributi) della squadra del Manchester United

Ecco come viene presentata la tua logica nelle immagini …

Questo è un classico esempio di composizione vs ereditarietà .

In questo caso specifico:

La squadra è una lista di giocatori con un comportamento aggiunto

o

La squadra è un object a sé stante che contiene un elenco di giocatori.

Estendendo List ti stai limitando in diversi modi:

  1. Non è ansible limitare l’accesso (ad esempio, impedire alle persone di cambiare il ruolo). Ottieni tutti i metodi di Lista se hai bisogno / li vuoi tutti o no.

  2. Cosa succede se vuoi avere anche elenchi di altre cose. Ad esempio, i team hanno allenatori, manager, tifosi, attrezzature, ecc. Alcuni di questi potrebbero essere elenchi a pieno titolo.

  3. Limiti le tue opzioni per l’ereditarietà. Ad esempio, potresti voler creare un object Team generico e poi avere BaseballTeam, FootballTeam, ecc. Che erediti da quello. Per ereditare da List devi fare l’ereditarietà dal Team, ma questo significa che tutti i vari tipi di team sono costretti ad avere la stessa implementazione di quel ruolo.

Composizione: include un object che fornisce il comportamento desiderato all’interno dell’object.

Ereditarietà: il tuo object diventa un’istanza dell’object che ha il comportamento che desideri.

Entrambi hanno i loro usi, ma questo è un chiaro caso in cui è preferibile la composizione.

Come tutti hanno sottolineato, una squadra di giocatori non è una lista di giocatori. Questo errore è causato da molte persone ovunque, forse a vari livelli di esperienza. Spesso il problema è sottile e occasionalmente molto grave, come in questo caso. Tali progetti sono cattivi perché questi violano il Principio di sostituzione di Liskov . Internet ha molti buoni articoli che spiegano questo concetto, ad es. http://en.wikipedia.org/wiki/Liskov_substitution_principle

In sintesi, ci sono due regole da conservare in una relazione padre / figlio tra classi:

  • un bambino non dovrebbe richiedere caratteristiche inferiori a quelle che definiscono completamente il genitore.
  • un genitore non dovrebbe richiedere alcuna caratteristica oltre a ciò che definisce completamente il bambino.

In altre parole, un genitore è una definizione necessaria di un bambino e un figlio è una definizione sufficiente di un genitore.

Ecco un modo per pensare attraverso una soluzione e applicare il principio di cui sopra che dovrebbe aiutare a evitare un simile errore. Si dovrebbero testare le ipotesi verificando se tutte le operazioni di una class genitore sono valide per la class derivata sia strutturalmente che semanticamente.

  • Una squadra di calcio è una lista di giocatori di calcio? (Tutte le proprietà di un elenco si applicano a una squadra con lo stesso significato)
    • Una squadra è una raccolta di quadro omogenee? Sì, la squadra è una collezione di giocatori
    • L’ordine di inclusione dei giocatori è descrittivo dello stato della squadra e il team assicura che la sequenza venga conservata a meno che non venga esplicitamente modificato? No e No
    • I giocatori dovrebbero essere inclusi / eliminati in base alla loro posizione sequenziale nella squadra? No

Come vedi, solo la prima caratteristica di una lista è applicabile a una squadra. Quindi una squadra non è una lista. Un elenco sarebbe un dettaglio di implementazione di come gestisci il tuo team, quindi dovrebbe essere usato solo per memorizzare gli oggetti del giocatore ed essere manipolato con i metodi della class Team.

A questo punto vorrei sottolineare che una class di Team dovrebbe, a mio parere, non essere nemmeno implementata usando una Lista; dovrebbe essere implementato utilizzando una struttura di dati Set (ad esempio HashSet) nella maggior parte dei casi.

Cosa succede se FootballTeam ha una squadra riserve insieme alla squadra principale?

 class FootballTeam { List Players { get; set; } List ReservePlayers { get; set; } } 

Come lo modellerai?

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

La relazione ha chiaramente a e non è a .

o RetiredPlayers ?

 class FootballTeam { List Players { get; set; } List ReservePlayers { get; set; } List RetiredPlayers { get; set; } } 

Come regola generale, se si desidera ereditare da una raccolta, denominare la class SomethingCollection .

Semanticamente la tua SomethingCollection senso? Fallo solo se il tuo tipo è una raccolta di Something .

Nel caso di FootballTeam , non suona bene. Una Team è più di una Collection . Una Team può avere allenatori, allenatori, ecc. Come hanno sottolineato le altre risposte.

FootballCollection sembra una raccolta di palloni da calcio o forse una collezione di accessori per il calcio. TeamCollection , una raccolta di squadre.

FootballPlayerCollection sembra una raccolta di giocatori che sarebbe un nome valido per una class che eredita da List se davvero volevi farlo.

List reale List è un tipo perfettamente valido da affrontare. Forse IList se lo stai restituendo da un metodo.

In sintesi

Chiedilo a te stesso

  1. X a Y ? o ha X a Y ?

  2. I miei nomi di class significano cosa sono?

Prima di tutto, ha a che fare con l’usabilità. Se si utilizza l’ereditarietà, la class Team esporrà comportamenti (metodi) progettati esclusivamente per la manipolazione degli oggetti. Ad esempio, i AsReadOnly() o CopyTo(obj) non hanno senso per l’object team. Invece del metodo AddRange(items) probabilmente vorresti un metodo AddPlayers(players) più descrittivo.

Se si desidera utilizzare LINQ, l’implementazione di un’interfaccia generica come ICollection o IEnumerable avrebbe più senso.

Come già detto, la composizione è il modo giusto per farlo. Basta implementare un elenco di giocatori come variabile privata.

Design> Implementazione

Quali metodi e proprietà esponi sono una decisione di progettazione. La class base da cui si eredita è un dettaglio di implementazione. Sento che vale la pena fare un passo indietro verso il primo.

Un object è una raccolta di dati e comportamenti.

Quindi le tue prime domande dovrebbero essere:

  • Quali dati comprende questo object nel modello che sto creando?
  • What behaviour does this object exhibit in that model?
  • How might this change in future?

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

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

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

Consider Design Specifics

Take a look at List and IList 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() look like something you want?

This isn’t a trick question; the answer might genuinely be “yes”. If you implement/inherit List or IList, 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 { ... } 

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 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 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 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 classs 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, ICollection, ICollection { .... } 

Design notwithstanding, at this point in C# you won’t be able to implement all of these by inheriting from List 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.Count and IList.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 classs 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 classs; but it may be suggestive.

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 { public string TeamName; public int RunningTotal } 

and this is correct:

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

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 formsr 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 footballTeam = ... 

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

 type FootballTeam = IList 

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.

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 classs. And think about inheritance.

Does allowing people to say

 myTeam.subList(3, 5); 

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

Just because I think the other answers pretty much go off on a tangent of whether a football team “is-a” List or “has-a” List , which really doesn’t answer this question as written.

The OP chiefly asks for clarification on guidelines for inheriting from List :

A guideline says that you shouldn’t inherit from List . Perchè no?

Because List 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 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 { 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 and fix up any compile errors:

  class FootballTeam : IList { private List 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.

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.

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. Questo è tutto. 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.

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; per esempio:

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.

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.

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") 

o

 team.ByName("Nicolas") 

Really. Do you have any doubt? Now maybe you need to play with other technical constraints that prevent you to use List 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.

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

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.

I just wanted to add that Bertrand Meyer, the inventor of Eiffel and design by contract, would have Team inherit from List 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 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 , the caller will use Add and Remove to modify it, but your virtual methods Hire and Fire will be called.

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 , you can allow your Team class to support Linq query extensions, without publicly exposing all the methods and properties of List .

 class Team : IEnumerable { private readonly List playerList; public Team() { playerList = new List(); } public Enumerator GetEnumerator() { return playerList.GetEnumerator(); } ... } class Player { ... } 

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

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