parameter - t<> c#




Por que não herdar de List<T>? (18)

Por último, alguns sugerem envolver a lista em algo:

Esse é o caminho correto. "Desnecessariamente wordy" é uma maneira ruim de ver isso. Tem um significado explícito quando você escreve my_team.Players.Count . Você quer contar os jogadores.

my_team.Count

..não significa nada. Conte o que?

Uma equipe não é uma lista - consiste em mais do que apenas uma lista de jogadores. Uma equipe possui jogadores, então os jogadores devem fazer parte dela (um membro).

Se você está realmente preocupado com o fato de ser excessivamente detalhado, é sempre possível expor propriedades da equipe:

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

..que se torna:

my_team.PlayerCount

Isso tem significado agora e adere à Lei de Demeter .

Você também deve considerar aderir ao princípio de reutilização Composite . Ao herdar de List<T> , você está dizendo que uma equipe é uma lista de jogadores e expõe métodos desnecessários dela. Isso é incorreto - como você afirmou, uma equipe é mais do que uma lista de jogadores: tem um nome, gerentes, membros do conselho, treinadores, equipe médica, limites salariais, etc. Por ter sua classe de equipe contendo uma lista de jogadores, você está dizendo "Uma equipe tem uma lista de jogadores", mas também pode ter outras coisas.

Ao planejar meus programas, muitas vezes começo com uma corrente de pensamento como esta:

Um time de futebol é apenas uma lista de jogadores de futebol. Portanto, eu deveria representá-lo com:

var football_team = new List<FootballPlayer>();

A ordenação desta lista representa a ordem em que os jogadores são listados na lista.

Mas percebo depois que as equipes também têm outras propriedades, além da simples lista de jogadores, que devem ser registradas. Por exemplo, o total de pontuações nesta temporada, o orçamento atual, as cores uniformes, uma string representando o nome da equipe, etc.

Então eu penso:

Ok, um time de futebol é como uma lista de jogadores, mas, além disso, tem um nome (uma string ) e um total de pontuações (um int ). O .NET não fornece uma classe para armazenar times de futebol, então eu farei minha própria aula. A estrutura existente mais semelhante e relevante é List<FootballPlayer> , então eu herdarei dela:

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

Mas acontece que uma diretriz diz que você não deve herdar de List<T> . Estou completamente confuso por esta diretriz em dois aspectos.

Por que não?

Aparentemente List é de alguma forma otimizado para desempenho . Como assim? Quais problemas de desempenho causarei se eu estender a List ? O que exatamente vai quebrar?

Outra razão que eu vi é que a List é fornecida pela Microsoft, e eu não tenho controle sobre ela, então não posso alterá-la mais tarde, depois de expor uma "API pública" . Mas eu me esforço para entender isso. O que é uma API pública e por que devo me importar? Se o meu projeto atual não tem e provavelmente nunca terá essa API pública, posso ignorar com segurança essa diretriz? Se eu herdar de List e acabar precisando de uma API pública, que dificuldades terei?

Por que isso importa? Uma lista é uma lista. O que poderia mudar? O que eu poderia querer mudar?

E por último, se a Microsoft não quisesse que eu herda da List , por que eles não sealed a classe?

O que mais devo usar?

Aparentemente, para coleções personalizadas, a Microsoft forneceu uma classe Collection que deve ser estendida em vez de List . Mas essa classe é muito simples e não tem muitas coisas úteis, como AddRange , por exemplo. A resposta de jvitor83 fornece uma justificativa de desempenho para esse método específico, mas como um AddRange lento não é melhor que nenhum AddRange ?

Herdar da Collection é muito mais trabalho do que herdar da List , e não vejo nenhum benefício. Certamente a Microsoft não me disse para fazer um trabalho extra sem motivo, então não posso deixar de sentir que estou de alguma forma entendendo mal alguma coisa, e herdar o Collection não é a solução certa para o meu problema.

Eu vi sugestões como implementar o IList . Apenas não. São dezenas de linhas de código clichê que não me dão nada.

Por último, alguns sugerem envolver a List em algo:

class FootballTeam 
{ 
    public List<FootballPlayer> Players; 
}

Existem dois problemas com isso:

  1. Isso torna meu código desnecessariamente detalhado. Agora devo chamar my_team.Players.Count vez de apenas my_team.Count . Felizmente, com o C #, posso definir indexadores para tornar a indexação transparente e encaminhar todos os métodos da List interna ... Mas isso é muito código! O que eu ganho com todo esse trabalho?

  2. Simplesmente não faz nenhum sentido. Um time de futebol não "tem" uma lista de jogadores. É a lista de jogadores. Você não diz "John McFootballer se juntou aos jogadores da SomeTeam". Você diz "John se juntou a SomeTeam". Você não adiciona uma letra a "caracteres de uma string", você adiciona uma letra a uma string. Você não adiciona um livro aos livros de uma biblioteca e adiciona um livro a uma biblioteca.

Eu percebo que o que acontece "sob o capô" pode ser dito como "adicionando X à lista interna de Y", mas isso parece ser uma maneira muito contra-intuitiva de pensar sobre o mundo.

Minha pergunta (resumida)

Qual é o modo C # correto de representar uma estrutura de dados, que, "logicamente" (isto é, "para a mente humana") é apenas uma list de things com alguns sinos e assobios?

Herdar de List<T> sempre inaceitável? Quando é aceitável? Porque porque não? O que um programador deve considerar ao decidir se herdará de List<T> ou não?


Qual é a maneira C # correta de representar uma estrutura de dados ...

Lembre-se, "Todos os modelos estão errados, mas alguns são úteis". - George EP Box

Não existe um "caminho correto", apenas um útil.

Escolha um que seja útil para você e / para seus usuários. É isso aí. Desenvolva-se economicamente, não faça engenharia excessiva. Quanto menos código você escrever, menos código precisará depurar. (leia as seguintes edições).

- Editado

Minha melhor resposta seria ... depende. Herdar de uma Lista exporia os clientes desta classe a métodos que podem não ser expostos, principalmente porque o FootballTeam se parece com uma entidade de negócios.

- 2ª edição

Eu sinceramente não lembro o que eu estava me referindo no comentário "não engenhei demais". Embora eu acredite que a mentalidade KISS seja um bom guia, quero enfatizar que herdar uma classe de negócios da Lista criaria mais problemas do que resolve, devido ao vazamento de abstração .

Por outro lado, acredito que há um número limitado de casos em que simplesmente herdar da Lista é útil. Como escrevi na edição anterior, isso depende. A resposta para cada caso é fortemente influenciada pelo conhecimento, experiência e preferências pessoais.

Obrigado ao @kai por me ajudar a pensar com mais precisão sobre a resposta.


Preferir Interfaces sobre Classes

As classes devem evitar derivar de classes e, em vez disso, implementar as interfaces mínimas necessárias.

Quebras de herança Encapsulamento

Derivando de classes quebra o encapsulamento :

  • expõe detalhes internos sobre como sua coleção é implementada
  • declara uma interface (conjunto de funções e propriedades públicas) que pode não ser apropriada

Entre outras coisas, isso torna mais difícil refatorar seu código.

Classes são um detalhe de implementação

As classes são um detalhe de implementação que deve ser escondido de outras partes do seu código. Em suma, System.Listé uma implementação específica de um tipo de dados abstrato, que pode ou não ser apropriado agora e no futuro.

Conceitualmente, o fato de o System.Listtipo de dados ser chamado de "lista" é um pouco arriscado. A System.List<T>é uma coleção ordenada mutável que suporta operações O (1) amortizadas para adicionar, inserir e remover elementos e operações O (1) para recuperar o número de elementos ou obter e definir elemento por índice.

Quanto menor a interface, mais flexível é o código

Ao projetar uma estrutura de dados, quanto mais simples for a interface, mais flexível será o código. Basta ver como o LINQ é poderoso para uma demonstração disso.

Como escolher interfaces

Quando você pensa em "lista", você deve começar dizendo "Eu preciso representar uma coleção de jogadores de beisebol". Então, digamos que você decida modelar isso com uma classe. O que você deve fazer primeiro é decidir qual a quantidade mínima de interfaces que essa classe precisará expor.

Algumas perguntas que podem ajudar a orientar esse processo:

  • Preciso ter a contagem? Se não considerar implementarIEnumerable<T>
  • Esta coleção vai mudar depois de ter sido inicializada? Se não considerar IReadonlyList<T>.
  • É importante que eu possa acessar itens por índice? ConsiderarICollection<T>
  • A ordem em que adiciono itens à coleção é importante? Talvez seja um ISet<T>?
  • Se você realmente quer essas coisas, vá em frente e implemente IList<T>.

Desta forma, você não estará acoplando outras partes do código aos detalhes de implementação de sua coleção de jogadores de beisebol e estará livre para mudar a forma como ela é implementada, desde que você respeite a interface.

Ao adotar essa abordagem, você descobrirá que o código se torna mais fácil de ler, refatorar e reutilizar.

Notas sobre como evitar o clichê

Implementar interfaces em um IDE moderno deve ser fácil. Clique com o botão direito e escolha "Implementar interface". Em seguida, encaminhe todas as implementações para uma classe membro, se necessário.

Dito isso, se você perceber que está escrevendo muito clichê, é potencialmente porque você está expondo mais funções do que deveria. É a mesma razão pela qual você não deve herdar de uma aula.

Você também pode criar interfaces menores que façam sentido para seu aplicativo e talvez apenas algumas funções de extensão de ajuda para mapear essas interfaces para quaisquer outras que você precisar. Esta é a abordagem que fiz na minha própria IArrayinterface para a biblioteca LinqArray .


Design> Implementação

Quais métodos e propriedades você expõe são uma decisão de design. De qual classe base você herda é um detalhe de implementação. Eu sinto que vale a pena dar um passo atrás para o primeiro.

Um objeto é uma coleção de dados e comportamento.

Então suas primeiras perguntas devem ser:

  • Quais dados esse objeto compõe no modelo que estou criando?
  • Qual comportamento esse objeto exibe nesse modelo?
  • Como isso pode mudar no futuro?

Tenha em mente que a herança implica um relacionamento "isa" (é a), enquanto a composição implica um relacionamento "has a" (hasa). Escolha o caminho certo para a sua situação na sua opinião, tendo em mente onde as coisas podem ir à medida que sua aplicação evolui.

Considere pensar em interfaces antes de pensar em tipos concretos, pois algumas pessoas acham mais fácil colocar seu cérebro no "modo de design" dessa maneira.

Isso não é algo que todos fazem conscientemente a esse nível no dia a dia da codificação. Mas se você está ponderando este tipo de assunto, você está pisando em águas de design. Estar ciente disso pode ser libertador.

Considere design específicos

Dê uma olhada no List <T> e IList <T> no MSDN ou no Visual Studio. Veja quais métodos e propriedades eles expõem. Todos estes métodos se parecem com algo que alguém gostaria de fazer para um FootballTeam na sua opinião?

O footballTeam.Reverse () faz sentido para você? O footballTeam.ConvertAll <TOutput> () parece com algo que você quer?

Esta não é uma pergunta complicada; a resposta pode ser genuinamente "sim". Se você implementar / herdar List <Player> ou IList <Player>, você está preso a eles; se isso é ideal para o seu modelo, faça isso.

Se você decidir sim, isso faz sentido, e você quer que seu objeto seja tratável como uma coleção / lista de jogadores (comportamento) e, portanto, você deseja implementar ICollection ou IList, por todos os meios fazê-lo. Notionalmente:

class FootballTeam : ... ICollection<Player>
{
    ...
}

Se você quiser que o seu objeto contenha uma coleção / lista de jogadores (dados) e, portanto, deseja que a coleção ou lista seja uma propriedade ou um membro, faça isso de todas as maneiras. Notionalmente:

class FootballTeam ...
{
    public ICollection<Player> Players { get { ... } }
}

Você pode achar que deseja que as pessoas só possam enumerar o conjunto de jogadores, em vez de contá-las, adicioná-las ou removê-las. IEnumerable <Player> é uma opção perfeitamente válida a considerar.

Você pode achar que nenhuma dessas interfaces é útil em seu modelo. Isso é menos provável (IEnumerable <T> é útil em muitas situações), mas ainda é possível.

Qualquer um que tente dizer-lhe que um deles é categoricamente e definitivamente errado em todos os casos é equivocado. Qualquer um que tente lhe dizer é categoricamente e definitivamente certo em todos os casos é equivocado.

Mover para Implementação

Depois de decidir os dados e o comportamento, você pode tomar uma decisão sobre a implementação. Isso inclui quais classes concretas você depende de herança ou composição.

Isso pode não ser um grande passo, e as pessoas geralmente combinam design e implementação, já que é possível passar por tudo isso em sua cabeça em um segundo ou dois e começar a digitar.

Uma experiência de pensamento

Um exemplo artificial: como outros já mencionaram, uma equipe nem sempre é "apenas" uma coleção de jogadores. Você mantém uma coleção de pontuações de jogo para a equipe? A equipe é intercambiável com o clube em seu modelo? Se sim, e se sua equipe for uma coleção de jogadores, talvez seja também uma coleção de funcionários e / ou uma coleção de pontuações. Então você acaba com:

class FootballTeam : ... ICollection<Player>, 
                         ICollection<StaffMember>,
                         ICollection<Score>
{
    ....
}

Design não obstante, neste ponto em C # você não será capaz de implementar tudo isso herdando de List <T> de qualquer maneira, já que o C # "only" suporta herança simples. (Se você já tentou este malarky em C ++, você pode considerar isso uma coisa boa.) Implementar uma recolha através de herança e uma via composição é susceptível de se sentir sujo. E propriedades como Count tornam-se confusas para os usuários, a menos que você implemente ILIst <Player> .Count e IList <StaffMember> .Count etc. explicitamente, e eles são apenas dolorosos e não confusos. Você pode ver para onde isso está indo; Por outro lado, pensar nisso, embora pense bem nesse caminho, pode lhe dizer que é errado seguir nessa direção (e, com razão ou sem razão, seus colegas também podem se você o implementou dessa maneira!)

A resposta curta (tarde demais)

A diretriz sobre não herdar de classes de coleções não é específica do C #, você encontrará em muitas linguagens de programação. É recebido sabedoria e não uma lei. Uma das razões é que, na prática, a composição é considerada, muitas vezes, uma herança hereditária em termos de compreensibilidade, implementação e sustentabilidade. É mais comum com objetos do mundo real / domínio encontrar relacionamentos "hasa" úteis e consistentes do que relacionamentos "isa" úteis e consistentes, a menos que você esteja no fundo, especialmente com o passar do tempo e os dados e comportamentos precisos dos objetos no código alterar. Isso não deve fazer com que você descarte sempre a herança de classes de coleção; mas pode ser sugestivo.


E se o FootballTeam tiver uma equipe de reservas junto com a equipe principal?

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

Como você modelaria isso com?

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

O relacionamento é claramente tem um e não é um .

ou RetiredPlayers ?

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

Como regra geral, se você quiser herdar de uma coleção, nomeie a classe como SomethingCollection .

O seu SomethingCollection semanticamente faz sentido? Só faça isso se o seu tipo for uma coleção de Something .

No caso do FootballTeam , não parece certo. Uma Team é mais que uma Collection . Uma Team pode ter treinadores, treinadores, etc, como as outras respostas apontaram.

FootballCollection parece uma coleção de bolas de futebol ou talvez uma coleção de parafernália de futebol. TeamCollection , uma coleção de equipes.

FootballPlayerCollection soa como uma coleção de jogadores que seria um nome válido para uma classe que herda de List<FootballPlayer> se você realmente quisesse fazer isso.

Really List<FootballPlayer> é um tipo perfeitamente bom de se lidar. Talvez IList<FootballPlayer> se você está retornando de um método.

Em suma

Pergunte a si mesmo

  1. X é um Y ? ou tem X a Y ?

  2. Os meus nomes de classe significam o que são?


Este é um exemplo clássico de composition versus inheritance .

Neste caso específico:

A equipe é uma lista de jogadores com comportamento adicionado

ou

A equipe é um objeto próprio que contém uma lista de jogadores?

Ao estender List, você está se limitando de várias maneiras:

  1. Você não pode restringir o acesso (por exemplo, parando as pessoas alterando a lista). Você obtém todos os métodos da Lista, quer precise / queira todos ou não.

  2. O que acontece se você quiser ter listas de outras coisas também. Por exemplo, as equipes têm treinadores, gerentes, torcedores, equipamentos, etc. Algumas delas podem ser listas por direito próprio.

  3. Você limita suas opções de herança. Por exemplo, você pode querer criar um objeto Team genérico e depois ter BaseballTeam, FootballTeam, etc., que herdam disso. Para herdar da Lista, você precisa fazer a herança da Equipe, mas isso significa que todos os vários tipos de equipe são obrigados a ter a mesma implementação dessa lista.

Composição - incluindo um objeto dando o comportamento que você deseja dentro do seu objeto.

Herança - seu objeto se torna uma instância do objeto que possui o comportamento desejado.

Ambos têm seus usos, mas este é um caso claro onde a composição é preferível.


Primeiro de tudo, tem a ver com usabilidade. Se você usar herança, a classe Team irá expor o comportamento (métodos) que são projetados apenas para manipulação de objetos. Por exemplo, os AsReadOnly() ou CopyTo(obj) não fazem sentido para o objeto de equipe.Em vez do AddRange(items)método, você provavelmente desejaria um AddPlayers(players)método mais descritivo .

Se você quiser usar o LINQ, implementar uma interface genérica como ICollection<T>ou IEnumerable<T>faria mais sentido.

Como mencionado, a composição é o caminho certo para fazê-lo. Basta implementar uma lista de jogadores como uma variável privada.


Uau, o seu post tem uma enorme quantidade de perguntas e pontos. A maior parte do raciocínio que você recebe da Microsoft é exatamente no ponto. Vamos começar com tudo sobre List<T>

  • List<T> é altamente otimizado. Seu principal uso é ser usado como um membro particular de um objeto.
  • A Microsoft não o fechou porque às vezes você pode querer criar uma classe que tenha um nome mais amigável: class MyList<T, TX> : List<CustomObject<T, Something<TX>> { ... } . Agora é tão fácil quanto fazer var list = new MyList<int, string>(); .
  • CA1002: Não exponha listas genéricas : Basicamente, mesmo se você planeja usar este aplicativo como o único desenvolvedor, vale a pena desenvolver com boas práticas de codificação, para que elas sejam incutidas em você e na segunda natureza. Você ainda pode expor a lista como um IList<T> se precisar que algum consumidor tenha uma lista indexada. Isso permite que você mude a implementação dentro de uma classe mais tarde.
  • Microsoft fez Collection<T> muito genérica porque é um conceito genérico ... o nome diz tudo; é apenas uma coleção. Existem versões mais precisas, como SortedCollection<T> , ObservableCollection<T> , ReadOnlyCollection<T> , etc., cada uma das quais implementa IList<T> mas não List<T> .
  • Collection<T> permite que os membros (isto é, Adicionar, Remover, etc.) sejam substituídos porque são virtuais. List<T> não.
  • A última parte da sua pergunta está no local. Um time de futebol é mais do que apenas uma lista de jogadores, então deve ser uma classe que contenha essa lista de jogadores. Pense composição vs herança . Um time de futebol tem uma lista de jogadores (uma lista), não é uma lista de jogadores.

Se eu estivesse escrevendo esse código, a classe provavelmente seria algo assim:

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

Deixe-me reescrever sua pergunta. então você pode ver o assunto de uma perspectiva diferente.

Quando eu preciso representar um time de futebol, entendo que é basicamente um nome. Como: "as águias"

string team = new string();

Então mais tarde percebi que as equipes também têm jogadores.

Por que não posso simplesmente estender o tipo de string para que ele também tenha uma lista de players?

Seu ponto de entrada no problema é arbitrário. Tente pensar o que uma equipe tem (propriedades), não o que é .

Depois disso, você poderá ver se ele compartilha propriedades com outras classes. E pense em herança.


Depende do comportamento do seu objeto "equipe". Se ele se comporta como uma coleção, pode ser correto representá-lo primeiro com uma lista simples. Então você pode começar a notar que você continua duplicando o código que itera na lista; Neste ponto, você tem a opção de criar um objeto do FootballTeam que encapsula a lista de jogadores. A classe FootballTeam se torna o lar de todo o código que interage na lista de jogadores.

Isso torna meu código desnecessariamente detalhado. Agora devo chamar my_team.Players.Count em vez de apenas my_team.Count. Felizmente, com o C #, posso definir indexadores para tornar a indexação transparente e encaminhar todos os métodos da List interna ... Mas isso é muito código! O que eu ganho com todo esse trabalho?

Encapsulamento. Seus clientes não precisam saber o que acontece dentro do FootballTeam. Para todos os seus clientes sabem, pode ser implementado olhando a lista de jogadores em um banco de dados. Eles não precisam saber e isso melhora seu design.

Simplesmente não faz nenhum sentido. Um time de futebol não "tem" uma lista de jogadores. É a lista de jogadores. Você não diz "John McFootballer se juntou aos jogadores da SomeTeam". Você diz "John se juntou a SomeTeam". Você não adiciona uma letra a "caracteres de uma string", você adiciona uma letra a uma string. Você não adiciona um livro aos livros de uma biblioteca e adiciona um livro a uma biblioteca.

Exatamente :) você dirá footballTeam.Add (john), não footballTeam.List.Add (john). A lista interna não estará visível.


Eu acho que não concordo com sua generalização. Uma equipe não é apenas uma coleção de jogadores. Uma equipe tem muito mais informações sobre isso - nome, emblema, coleção de equipe administrativa / administrativa, coleção de equipe técnica e, em seguida, coleção de jogadores. Então, corretamente, sua classe do FootballTeam deve ter 3 coleções e não ser uma coleção; se é para modelar adequadamente o mundo real.

Você pode considerar uma classe PlayerCollection que, como o StringCollection especializado, oferece alguns outros recursos - como validação e verificações antes que os objetos sejam adicionados ou removidos do armazenamento interno.

Talvez a noção de um apostador do PlayerCollection seja mais adequada?

public class PlayerCollection : Collection<Player> 
{ 
}

E então o FootballTeam pode ser assim:

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

Eu só queria acrescentar que Bertrand Meyer, o inventor de Eiffel e design por contrato, teria Teamherdado de List<Player>sem sequer uma pálpebra.

Em seu livro, Construção de Software Orientado a Objetos , ele discute a implementação de um sistema de GUI onde janelas retangulares podem ter janelas filhas. Ele simplesmente Windowherdou de ambos Rectanglee Tree<Window>reutilizou a implementação.

No entanto, c # não é Eiffel. O último suporta herança múltipla e renomeação de recursos . Em C #, quando você subclasse, você herda a interface e a implementação. Você pode substituir a implementação, mas as convenções de chamada são copiadas diretamente da superclasse. Em Eiffel, no entanto, você pode modificar os nomes dos métodos públicos, para que você possa renomear Adde Removepara Hiree Fireno seu Team. Se uma instância Teamé upcast de volta para List<Player>o chamador vai usar Adde Removemodificá-lo, mas seus métodos virtuais Hiree Fireserá chamado.


Isso me lembra do "é um" versus "tem um" tradeoff. Às vezes é mais fácil e faz mais sentido herdar diretamente de uma superclasse. Outras vezes, faz mais sentido criar uma classe autônoma e incluir a classe da qual você teria herdado como variável de membro. Você ainda pode acessar a funcionalidade da classe, mas não está vinculado à interface ou a quaisquer outras restrições que possam vir da herança da classe.

O que você faz? Tal como acontece com muitas coisas ... depende do contexto. O guia que eu usaria é que, para herdar de outra classe, deveria haver um relacionamento "é um". Então, se você está escrevendo uma classe chamada BMW, ela pode herdar de um carro, porque um BMW realmente é um carro. Uma classe Horse pode herdar da classe Mamífero porque um cavalo é na verdade um mamífero na vida real e qualquer funcionalidade Mammal deve ser relevante para Horse. Mas você pode dizer que uma equipe é uma lista? Pelo que posso dizer, não parece que uma equipe realmente "é uma" lista. Então, neste caso, eu teria uma lista como uma variável de membro.


Meu segredo sujo: eu não me importo com o que as pessoas dizem, e eu faço isso. O .NET Framework é distribuído com "XxxxCollection" (UIElementCollection para o exemplo da parte superior da minha cabeça).

Então, o que me impede de dizer:

team.Players.ByName("Nicolas")

Quando eu acho melhor que

team.ByName("Nicolas")

Além disso, meu PlayerCollection pode ser usado por outra classe, como "Club", sem qualquer duplicação de código.

club.Players.ByName("Nicolas")

As melhores práticas de ontem podem não ser as de amanhã. Não há razão por trás da maioria das melhores práticas, a maioria é apenas ampla concordância entre a comunidade. Em vez de perguntar à comunidade se ela vai culpá-lo quando fizer isso, pergunte a si mesmo: o que é mais legível e sustentável?

team.Players.ByName("Nicolas") 

ou

team.ByName("Nicolas")

Mesmo. Você tem alguma dúvida? Agora talvez você precise jogar com outras restrições técnicas que impedem que você use List <T> em seu caso de uso real. Mas não adicione uma restrição que não deveria existir. Se a Microsoft não documentou o porquê, então é certamente uma "melhor prática" vinda do nada.


Quando eles dizem que List<T>é "otimizado", eu acho que eles querem dizer que ele não tem recursos como métodos virtuais que são um pouco mais caros. Portanto, o problema é que, quando você expõe List<T>sua API pública , perde a capacidade de impor regras comerciais ou personalizar sua funcionalidade posteriormente. Mas se você estiver usando essa classe herdada como interna em seu projeto (em oposição a potencialmente exposta a milhares de seus clientes / parceiros / outras equipes como API), pode ser OK se economizar seu tempo e for a funcionalidade que você deseja duplicado. A vantagem de herdar List<T>é que você elimina muito código de wrap idiota que nunca será personalizado em um futuro previsível. Além disso, se você quiser que sua classe tenha explicitamente a mesma semânticaList<T> para a vida de suas APIs, então também pode ser OK.

Muitas vezes vejo muitas pessoas fazendo toneladas de trabalho extra apenas porque a regra FxCop diz isso ou o blog de alguém diz que é uma prática "ruim". Muitas vezes, isso transforma o código em um padrão de design de estranheza palooza. Como com muita diretriz, trate como orientação que pode ter exceções.


Só porque eu acho que as outras respostas praticamente se importam em saber se um time de futebol "é-um" List<FootballPlayer>ou "tem-um" List<FootballPlayer>, o que realmente não responde a essa pergunta como está escrito.

O OP pede principalmente esclarecimentos sobre diretrizes para herdar de List<T>:

Uma diretriz diz que você não deve herdar List<T>. Por que não?

Porque List<T>não tem métodos virtuais. Isso é um problema menor em seu próprio código, já que geralmente você pode mudar a implementação com relativamente pouca dor - mas pode ser um problema muito maior em uma API pública.

O que é uma API pública e por que devo me importar?

Uma API pública é uma interface que você expõe a programadores de terceiros. Pense no código do framework. E lembre-se de que as diretrizes referenciadas são as " Diretrizes de Design do .NET Framework " e não as " Diretrizes de Design de Aplicativos .NET ". Há uma diferença e, em geral, o design da API pública é muito mais rigoroso.

Se o meu projeto atual não tem e provavelmente nunca terá essa API pública, posso ignorar com segurança essa diretriz? Se eu herdar de List e acabar precisando de uma API pública, que dificuldades terei?

Muito bem, sim. Você pode considerar o raciocínio por trás dele para ver se ele se aplica à sua situação, mas se você não estiver criando uma API pública, não precisa se preocupar com as APIs, como o controle de versão (das quais, isso é uma subconjunto).

Se você adicionar uma API pública no futuro, precisará abstrair sua API de sua implementação (não expondo List<T>diretamente) ou violar as diretrizes com a possível dor futura que isso acarreta.

Por que isso importa? Uma lista é uma lista. O que poderia mudar? O que eu poderia querer mudar?

Depende do contexto, mas como estamos usando FootballTeamcomo exemplo - imagine que você não pode adicionar um FootballPlayercaso isso faria com que a equipe ultrapassasse o teto salarial. Uma maneira possível de adicionar isso seria algo como:

 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 ... mas você não pode override Addporque não é virtual(por razões de desempenho).

Se você estiver em um aplicativo (o que, basicamente, significa que você e todos os seus chamadores são compilados juntos), você pode agora mudar para usar IList<T>e corrigir quaisquer erros de compilação:

 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 */
 }

mas, se você expôs publicamente a um terceiro você acabou de fazer uma alteração que causará erros de compilação e / ou tempo de execução.

TL; DR - as diretrizes são para APIs públicas. Para APIs privadas, faça o que quiser.


Um time de futebol não é uma lista de jogadores de futebol. Um time de futebol é composto de uma lista de jogadores de futebol!

Isso é logicamente errado:

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

e isso está correto:

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

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

Código anterior significa: um monte de caras da rua jogando futebol, e eles têm um nome. Algo como:

Enfim, esse código (da minha resposta)

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

Meios: este é um time de futebol que tem administração, jogadores, administradores, etc. Algo como:

É assim que sua lógica é apresentada em fotos ...





inheritance