¿Por qué no heredar de List ?

Cuando planifico mis progtwigs, a menudo empiezo con una cadena de pensamiento como esta:

Un equipo de fútbol es solo una lista de jugadores de fútbol. Por lo tanto, debería representarlo con:

var football_team = new List(); 

El orden de esta lista representa el orden en que se enumeran los jugadores en la lista.

Pero luego me doy cuenta de que los equipos también tienen otras propiedades, además de la mera lista de jugadores, que deben registrarse. Por ejemplo, el total acumulado de puntajes esta temporada, el presupuesto actual, los colores uniformes, una string representa el nombre del equipo, etc.

Entonces pienso:

De acuerdo, un equipo de fútbol es como una lista de jugadores, pero, además, tiene un nombre (una string ) y un total acumulado de puntajes (un int ). .NET no proporciona una clase para almacenar equipos de fútbol, ​​por lo que haré mi propia clase. La estructura existente más similar y relevante es List , por lo que heredaré de ella:

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

Pero resulta que una guía dice que no debe heredar de List . Estoy completamente confundido por esta guía en dos aspectos.

Por qué no?

Al parecer, List está optimizado de alguna manera para el rendimiento . ¿Cómo es eso? ¿Qué problemas de rendimiento causaré si extiendo List ? ¿Qué exactamente se romperá?

Otra razón que he visto es que Microsoft proporciona List y no tengo control sobre él, por lo que no puedo cambiarlo más tarde, después de exponer una “API pública” . Pero me cuesta entender esto. ¿Qué es una API pública y por qué debería importarme? Si mi proyecto actual no tiene esta API pública y es probable que nunca la tenga, ¿puedo ignorar esta guía de forma segura? Si heredo de List y resulta que necesito una API pública, ¿qué dificultades tendré?

¿Por qué incluso importa? Una lista es una lista. ¿Qué podría posiblemente cambiar? ¿Qué podría querer cambiar?

Y, por último, si Microsoft no quería que heredara de List , ¿por qué no sealed la clase?

¿Qué más se supone que debo usar?

Aparentemente, para las colecciones personalizadas, Microsoft ha proporcionado una clase Collection que debería extenderse en lugar de List . Pero esta clase es muy simple, y no tiene muchas cosas útiles, como AddRange , por ejemplo. La respuesta de jvitor83 proporciona una lógica de rendimiento para ese método en particular, pero ¿cómo es un AddRange lento no mejor que no AddRange ?

Heredar de Collection es mucho más trabajo que heredar de List , y no veo ningún beneficio. Seguramente Microsoft no me dirá que haga un trabajo extra sin ninguna razón, así que no puedo evitar sentir que de alguna manera estoy malinterpretando algo, y heredar Collection realidad no es la solución correcta para mi problema.

He visto sugerencias como implementar IList . Simplemente no. Esto es docenas de líneas de código repetitivo que no me gana nada.

Por último, algunos sugieren envolver la List en algo:

 class FootballTeam { public List Players; } 

Hay dos problemas con esto:

  1. Hace que mi código sea innecesariamente detallado. Ahora debo llamar my_team.Players.Count lugar de solo my_team.Count . Afortunadamente, con C # puedo definir indexadores para que la indexación sea transparente y reenviar todos los métodos de la List interna … ¡Pero eso es mucho código! ¿Qué obtengo por todo ese trabajo?

  2. Simplemente no tiene ningún sentido. Un equipo de fútbol no “tiene” una lista de jugadores. Es la lista de jugadores. Usted no dice “John McFootballer se ha unido a los jugadores de SomeTeam”. Usted dice “John se unió a SomeTeam”. No agrega una letra a “los caracteres de una cadena”, agrega una letra a una cadena. No agrega un libro a los libros de una biblioteca, agrega un libro a una biblioteca.

Me doy cuenta de que lo que sucede “debajo del capó” se puede decir que es “agregar X a la lista interna de Y”, pero esto parece una manera muy contraintuitiva de pensar sobre el mundo.

Mi pregunta (resumida)

¿Cuál es la forma correcta de C # de representar una estructura de datos, que “lógicamente” (es decir, “a la mente humana”) es solo una list de things con algunas sonrisas y silbidos?

¿Heredar de List siempre es inaceptable? ¿Cuándo es aceptable? ¿Por qué por qué no? ¿Qué debe considerar un progtwigdor al decidir si hereda de List o no?

Hay algunas buenas respuestas aquí. Les agregaría los siguientes puntos.

¿Cuál es la forma correcta de C # de representar una estructura de datos, que “lógicamente” (es decir, “a la mente humana”) es solo una lista de cosas con algunas sonrisas y silbidos?

Pídale a cualquier persona que no sea progtwigdora de computadoras que esté familiarizada con la existencia del fútbol que complete el espacio en blanco:

 A football team is a particular kind of _____ 

¿ Alguien dijo “lista de jugadores de fútbol con algunas sonrisas y silbidos”, o todos dijeron “equipo deportivo” o “club” u “organización”? Su noción de que un equipo de fútbol es un tipo particular de lista de jugadores está en su mente humana y su mente humana sola.

List es un mecanismo . El equipo de fútbol es un objeto comercial , es decir, un objeto que representa algún concepto que está en el dominio comercial del progtwig. ¡No mezcles esos! Un equipo de fútbol es una especie de equipo; tiene una lista, una lista es una lista de jugadores . Una lista no es un tipo particular de lista de jugadores . Una lista es una lista de jugadores. Entonces crea una propiedad llamada Roster que sea una List . Y ReadOnlyList mientras estás en ello, a menos que creas que todos los que saben sobre un equipo de fútbol pueden eliminar jugadores de la lista.

¿Heredar de List siempre es inaceptable?

Inaceptable para quien? ¿Yo? No.

¿Cuándo es aceptable?

Cuando está construyendo un mecanismo que amplía el mecanismo de List .

¿Qué debe considerar un progtwigdor al decidir si hereda de List o no?

¿Estoy construyendo un mecanismo o un objeto comercial ?

¡Pero eso es mucho código! ¿Qué obtengo por todo ese trabajo?

Pasó más tiempo escribiendo su pregunta que le habría llevado escribir métodos de reenvío para los miembros relevantes de List cincuenta veces más. Claramente no le temes a la verborrea, y estamos hablando de una cantidad muy pequeña de código aquí; esto es unos minutos de trabajo.

ACTUALIZAR

Lo pensé un poco más y hay otra razón para no modelar un equipo de fútbol como una lista de jugadores. De hecho, podría ser una mala idea modelar un equipo de fútbol como tener una lista de jugadores también. El problema con un equipo como tener una lista de jugadores es que lo que tienes es una instantánea del equipo en un momento determinado . No sé cuál es su caso de negocios para esta clase, pero si tuviera una clase que representara a un equipo de fútbol, ​​me gustaría formularle preguntas como “¿cuántos jugadores de los Seahawks se perdieron los juegos debido a una lesión entre 2003 y 2013?” o “¿Qué jugador de Denver que jugó anteriormente para otro equipo tuvo el mayor incremento anual en yardas?” o ” ¿Los Piggers fueron todo el camino este año? ”

Es decir, un equipo de fútbol me parece bien modelado como una colección de hechos históricos , como cuando un jugador fue reclutado, lesionado, retirado, etc. Obviamente, la lista actual de jugadores es un hecho importante que probablemente debería estar al frente y centro, pero puede haber otras cosas interesantes que desee hacer con este objeto que requieren una perspectiva más histórica.

Por último, algunos sugieren envolver la Lista en algo:

Esa es la forma correcta. “Innecesariamente prolijo” es una mala forma de ver esto. Tiene un significado explícito cuando escribes my_team.Players.Count . Quieres contar los jugadores.

 my_team.Count 

..no significa nada. ¿Cuenta qué?

Un equipo no es una lista: consiste en más que solo una lista de jugadores. Un equipo posee jugadores, por lo que los jugadores deben ser parte de él (un miembro).

Si realmente te preocupa que sea excesivamente detallado, siempre puedes exponer las propiedades del equipo:

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

..que se convierte en:

 my_team.PlayerCount 

Esto tiene un significado ahora y se adhiere a la Ley de Demeter .

También debería considerar adherirse al principio de reutilización compuesta . Al heredar de List , estás diciendo que un equipo es una lista de jugadores y expone los métodos innecesarios. Esto es incorrecto: como dijiste, un equipo es más que una lista de jugadores: tiene un nombre, gerentes, miembros de la junta, entrenadores, personal médico, topes salariales, etc. Al hacer que tu clase de equipo contenga una lista de jugadores, Está diciendo “Un equipo tiene una lista de jugadores”, pero también puede tener otras cosas.

Vaya, tu publicación tiene una gran cantidad de preguntas y puntos. La mayor parte del razonamiento que obtienes de Microsoft es exactamente correcto. Comencemos con todo sobre List

  • List está altamente optimizada. Su uso principal es para ser utilizado como un miembro privado de un objeto.
  • Microsoft no lo selló porque a veces es posible que desee crear una clase que tenga un nombre más amigable: class MyList : List> { ... } . Ahora es tan fácil como hacer var list = new MyList(); .
  • CA1002: No exponga listas genéricas : Básicamente, incluso si planea usar esta aplicación como único desarrollador, vale la pena desarrollarla con buenas prácticas de encoding, para que se inculquen en usted y en la naturaleza. Aún puede exponer la lista como IList si necesita que un consumidor tenga una lista indexada. Esto te permite cambiar la implementación dentro de una clase más adelante.
  • Microsoft hizo Collection muy genérico porque es un concepto genérico … el nombre lo dice todo; es solo una colección. Hay versiones más precisas como SortedCollection , ObservableCollection , ReadOnlyCollection , etc. cada una de las cuales implementa IList pero no List .
  • Collection permite anular los miembros (es decir, Agregar, Eliminar, etc.) porque son virtuales. List no.
  • La última parte de tu pregunta es perfecta. Un equipo de fútbol es más que solo una lista de jugadores, por lo que debe ser una clase que contenga esa lista de jugadores. Piensa en la composición frente a la herencia . Un equipo de fútbol tiene una lista de jugadores (una lista), no es una lista de jugadores.

Si estuviera escribiendo este código, la clase probablemente se vería así:

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

El código anterior significa: un grupo de chicos de la calle jugando al fútbol, ​​y tienen un nombre. Algo como:

Gente jugando al fútbol

De todos modos, este código (de mi respuesta)

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

Medios: este es un equipo de fútbol que tiene administración, jugadores, administradores, etc. Algo como:

Imagen que muestra los miembros (y otros atributos) del equipo del Manchester United

Así es como se presenta tu lógica en imágenes …

Este es un ejemplo clásico de composición vs herencia .

En este caso específico:

Es el equipo una lista de jugadores con comportamiento adicional

o

Es el equipo un objeto propio que contiene una lista de jugadores.

Al extender List, te estás limitando de varias maneras:

  1. No puede restringir el acceso (por ejemplo, detener a las personas que cambian la lista). Obtiene todos los métodos de Lista, ya sea que los necesite o quiera todos o no.

  2. ¿Qué pasa si quieres tener listas de otras cosas también? Por ejemplo, los equipos tienen entrenadores, gerentes, fanáticos, equipos, etc. Algunos de ellos bien podrían ser listas por derecho propio.

  3. Usted limita sus opciones de herencia. Por ejemplo, puede querer crear un objeto de equipo genérico, y luego tener BaseballTeam, FootballTeam, etc. que hereden de eso. Para heredar de List, debe hacer la herencia del equipo, pero eso significa que todos los distintos tipos de equipo están obligados a tener la misma implementación de esa lista.

Composición: incluye un objeto que proporciona el comportamiento que desea dentro de su objeto.

Herencia: su objeto se convierte en una instancia del objeto que tiene el comportamiento que desea.

Ambos tienen sus usos, pero este es un caso claro en el que la composición es preferible.

Como todos han señalado, un equipo de jugadores no es una lista de jugadores. Este error lo cometen muchas personas en todas partes, tal vez en varios niveles de experiencia. A menudo, el problema es sutil y, ocasionalmente, muy asqueroso, como en este caso. Tales diseños son malos porque violan el Principio de Sustitución de Liskov . Internet tiene muchos buenos artículos que explican este concepto, por ejemplo, http://en.wikipedia.org/wiki/Liskov_substitution_principle

En resumen, hay dos reglas que se deben preservar en una relación Padre / Hijo entre clases:

  • un niño no debería requerir ninguna característica menor a la que define completamente al padre.
  • un padre no debería requerir ninguna característica además de lo que define completamente al niño.

En otras palabras, un padre es una definición necesaria de un niño, y un niño es una definición suficiente de un padre.

Aquí hay una manera de pensar la solución de uno y aplicar el principio anterior que debería ayudar a evitar ese error. Se deben probar las hipótesis verificando si todas las operaciones de una clase padre son válidas para la clase derivada estructural y semánticamente.

  • ¿Es un equipo de fútbol una lista de jugadores de fútbol? (¿Todas las propiedades de una lista se aplican a un equipo en el mismo sentido)
    • ¿Es un equipo una colección de entidades homogéneas? Sí, el equipo es una colección de Jugadores
    • ¿El orden de inclusión de los jugadores es descriptivo del estado del equipo y el equipo se asegura de que la secuencia se conserve a menos que se modifique explícitamente? No y no
    • ¿Se espera que los jugadores sean incluidos / descartados en función de su posición secuencial en el equipo? No

Como puede ver, solo la primera característica de una lista es aplicable a un equipo. Por lo tanto, un equipo no es una lista. Una lista sería un detalle de implementación de cómo administras tu equipo, por lo que solo se debe usar para almacenar los objetos del jugador y se debe manipular con métodos de clase Team.

En este punto, me gustaría comentar que una clase de Equipo no debería implementarse en mi opinión utilizando una Lista; debería implementarse usando una estructura de datos Set (HashSet, por ejemplo) en la mayoría de los casos.

¿Qué sucede si FootballTeam tiene un equipo de reservas junto con el equipo principal?

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

¿Cómo modelarías eso?

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

La relación es claramente tiene a y no es a .

o RetiredPlayers ?

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

Como regla general, si alguna vez desea heredar de una colección, nombre la clase SomethingCollection .

¿Su SomethingCollection tiene sentido semánticamente? Solo haz esto si tu tipo es una colección de Something .

En el caso de FootballTeam , no suena bien. Un Team es más que una Collection . Un Team puede tener entrenadores, entrenadores, etc., como las otras respuestas han señalado.

FootballCollection suena como una colección de balones de fútbol o una colección de parafernalia de fútbol. TeamCollection , una colección de equipos.

FootballPlayerCollection suena como una colección de jugadores que sería un nombre válido para una clase que hereda de List si realmente quieres hacer eso.

Really List es un tipo perfectamente bueno para tratar. Tal vez IList si lo estás devolviendo desde un método.

En resumen

Pregúntese

  1. Es X una Y ? o tiene X a Y ?

  2. ¿Los nombres de mi clase significan lo que son?

En primer lugar, tiene que ver con la usabilidad. Si usa herencia, la clase Team expondrá el comportamiento (métodos) que están diseñados exclusivamente para la manipulación de objetos. Por ejemplo, los AsReadOnly() o CopyTo(obj) no tienen sentido para el objeto de equipo. En lugar del método AddRange(items) , es probable que desee un método AddPlayers(players) más descriptivo.

Si desea utilizar LINQ, implementar una interfaz genérica como ICollection o IEnumerable tendría más sentido.

Como se mencionó, la composición es la forma correcta de hacerlo. Solo implemente una lista de jugadores como una variable privada.

Diseño> Implementación

Qué métodos y propiedades expone es una decisión de diseño. De qué clase base heredaste es un detalle de implementación. Siento que vale la pena retroceder un paso hacia el anterior.

Un objeto es una colección de datos y comportamiento.

Entonces, sus primeras preguntas deberían ser:

  • ¿Qué datos comprende este objeto en el modelo que estoy creando?
  • ¿Qué comportamiento exhibe este objeto en ese modelo?
  • ¿Cómo podría cambiar esto en el futuro?

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

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 former players, etc.

Unclear semantics

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

Classes are extensible

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

Classes are more complex

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

Conclusion / Considerations

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

 IList 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 classes. 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 . Por qué 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?

Casi, sí. 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. Eso 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.

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; por ejemplo:

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

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