É melhor usar List<string>
em anotações de tipo ou StringList
ondeStringList
class StringList : List<String> { /* no further code!*/ }
c#
inheritance
Ruudjah
fonte
fonte
StringList
eList<string>
? Não existe, mas você (o genérico "você") conseguiu adicionar outro detalhe à base de código que é exclusiva apenas para o seu projeto e não significa nada para ninguém até que eles tenham estudado o código. Por que mascarar o nome de uma lista de strings apenas para continuar chamando de lista de strings? Por outro lado, se você quisesse,BagOBagels : List<string>
acho que faz mais sentido. Você pode iterá-lo como IEnumerable, consumidores externos não se importam com a implementação - bondade em todos os aspectos.Respostas:
Há outra razão pela qual você pode herdar de um tipo genérico.
A Microsoft recomenda evitar o aninhamento de tipos genéricos nas assinaturas de método .
É uma boa ideia, então, criar tipos nomeados no domínio comercial para alguns de seus genéricos. Em vez de ter um
IEnumerable<IDictionary<string, MyClass>>
, crie um tipoMyDictionary : IDictionary<string, MyClass>
e seu membro se tornará umIEnumerable<MyDictionary>
, o que é muito mais legível. Ele também permite que você use o MyDictionary em vários locais, aumentando a explicitação do seu código.Isso pode não parecer um grande benefício, mas no código comercial do mundo real eu vi genéricos que farão seu cabelo arrepiar. Coisas como
List<Tuple<string, Dictionary<int, List<Dictionary<string, List<double>>>>>
. O significado desse genérico não é de forma alguma óbvio. No entanto, se fosse construído com uma série de tipos criados explicitamente, a intenção do desenvolvedor original provavelmente seria muito mais clara. Além disso, se esse tipo genérico precisou ser alterado por algum motivo, encontrar e substituir todas as instâncias desse tipo genérico pode ser um problema, comparado com a alteração na classe derivada.Finalmente, há outra razão pela qual alguém pode querer criar seus próprios tipos derivados de genéricos. Considere as seguintes classes:
Agora, como sabemos qual
ICollection<MyItems>
passar para o construtor para MyConsumerClass? Se criarmos classes derivadasclass MyItems : ICollection<MyItems>
eclass MyItemCache : ICollection<MyItems>
, MyConsumerClass poderá ser extremamente específico sobre qual das duas coleções ela realmente deseja. Isso funciona muito bem com um contêiner de IoC, que pode resolver as duas coleções com muita facilidade. Ele também permite que o programador seja mais preciso - se faz sentidoMyConsumerClass
trabalhar com qualquer ICollection, eles podem usar o construtor genérico. Se, no entanto, nunca fizer sentido comercial usar um dos dois tipos derivados, o desenvolvedor poderá restringir o parâmetro do construtor ao tipo específico.Em resumo, geralmente vale a pena derivar tipos de tipos genéricos - ele permite um código mais explícito, quando apropriado, e ajuda na legibilidade e manutenção.
fonte
Em princípio, não há diferença entre herdar de tipos genéricos e herdar de qualquer outra coisa.
No entanto, por que diabos você derivaria de qualquer classe e não acrescentaria mais nada?
fonte
Não conheço muito bem o C #, mas acredito que esta é a única maneira de implementar um
typedef
, que os programadores C ++ geralmente usam por alguns motivos:Eles basicamente jogaram fora a última vantagem ao escolher nomes genéricos e chatos, mas os outros dois motivos ainda permanecem.
fonte
StringList
é umList<string>
- eles podem ser usados de forma intercambiável. Isso é importante ao trabalhar com outras APIs (ou ao expor sua API), pois todos os outros no mundo usamList<string>
.using StringList = List<string>;
é o equivalente C # mais próximo de um typedef. Subclassificar assim impedirá o uso de qualquer construtor com argumentos.using
restringe-a ao arquivo de código-fonte ondeusing
está colocado. Deste ponto de vista, a herança está mais próximatypedef
. E criar uma subclasse não impede que alguém adicione todos os construtores necessários (embora isso possa ser entediante).using
não pode ser usado para esse fim, mas a herança pode. Isso mostra por que eu discordo do comentário votado de Pete Kirkham - não considerousing
uma alternativa real aos C ++typedef
.Eu posso pensar em pelo menos uma razão prática.
Declarar tipos não genéricos que herdam de tipos genéricos (mesmo sem adicionar nada) permite que esses tipos sejam referenciados a partir de locais onde eles não estariam disponíveis ou disponíveis de uma maneira mais complicada do que deveriam.
Um desses casos é ao adicionar configurações através do editor de Configurações no Visual Studio, onde apenas tipos não genéricos parecem estar disponíveis. Se você for lá, notará que todas as
System.Collections
classes estão disponíveis, mas nenhuma coleção éSystem.Collections.Generic
exibida conforme aplicável.Outro caso é ao instanciar tipos por meio de XAML no WPF. Não há suporte para a passagem de argumentos de tipo na sintaxe XML ao usar um esquema anterior a 2009. Isso é agravado pelo fato de que o esquema de 2006 parece ser o padrão para novos projetos WPF.
fonte
Eu acho que você precisa investigar um pouco da história .
Caso contrário, Theodoros Chatzigiannakis teve as melhores respostas, por exemplo, ainda existem muitas ferramentas que não entendem tipos genéricos. Ou talvez até uma mistura de sua resposta e história.
fonte
Em alguns casos, é impossível usar tipos genéricos, por exemplo, você não pode fazer referência a um tipo genérico no XAML. Nesses casos, você pode criar um tipo não genérico que herda do tipo genérico que originalmente queria usar.
Se possível, eu evitaria isso.
Ao contrário de um typedef, você cria um novo tipo. Se você usar o StringList como um parâmetro de entrada para um método, seus chamadores não poderão passar a
List<string>
.Além disso, você precisaria copiar explicitamente todos os construtores que deseja usar, no exemplo StringList que você não poderia dizer
new StringList(new[]{"a", "b", "c"})
, mesmo queList<T>
defina esse construtor.Editar:
A resposta aceita se concentra nos profissionais ao usar os tipos derivados dentro do seu modelo de domínio. Eu concordo que eles possam ser úteis nesse caso, ainda assim, eu pessoalmente os evitaria mesmo lá.
Mas você deve (na minha opinião) nunca usá-los em uma API pública porque você dificulta a vida de quem chama ou perde desempenho (também não gosto de conversões implícitas em geral).
fonte
Para o que vale, aqui está um exemplo de como a herança de uma classe genérica é usada no compilador Roslyn da Microsoft e sem alterar o nome da classe. (Fiquei tão confuso com isso que acabei aqui na minha pesquisa para ver se isso era realmente possível.)
No projeto CodeAnalysis, você pode encontrar esta definição:
Então, no projeto CSharpCodeanalysis, há esta definição:
Essa classe PEModuleBuilder não genérica é usada extensivamente no projeto CSharpCodeanalysis e várias classes nesse projeto são herdadas, direta ou indiretamente.
E então, no projeto BasicCodeanalysis, há esta definição:
Como podemos (espero) supor que Roslyn foi escrita por pessoas com amplo conhecimento de C # e como deve ser usada, estou pensando que essa é uma recomendação da técnica.
fonte
Existem três razões possíveis pelas quais alguém tentaria fazer isso em cima da minha cabeça:
1) Para simplificar as declarações:
Claro
StringList
eListString
têm o mesmo comprimento, mas imagine que você esteja trabalhando com um exemploUserCollection
realDictionary<Tuple<string,Type>, IUserData<Dictionary,MySerializer>>
ou outro grande exemplo genérico.2) Para ajudar a AOT:
Às vezes, você precisa usar a compilação Ahead Of Time, não a Just In Time Compilation - por exemplo, desenvolvendo para iOS. Às vezes, o compilador AOT tem dificuldade em descobrir todos os tipos genéricos com antecedência, e isso pode ser uma tentativa de fornecer uma dica.
3) Para adicionar funcionalidade ou remover dependência:
Talvez eles tenham funcionalidades específicas para as quais desejam implementar
StringList
(podem estar ocultos em um método de extensão, ainda não foram adicionados ou históricos) aos quais não podem ser adicionadosList<string>
sem herdar dele ou que não desejam poluirList<string>
com .Como alternativa, eles podem alterar a implementação da
StringList
trilha, portanto, apenas usaram a classe como marcador por enquanto (normalmente você faria / usaria interfaces para isso, por exemploIList
).Eu também observaria que você encontrou esse código no Irony, que é um analisador de idiomas - um tipo de aplicativo bastante incomum. Pode haver algum outro motivo específico pelo qual isso foi usado.
Esses exemplos demonstram que pode haver razões legítimas para escrever tal declaração - mesmo que não seja muito comum. Como em qualquer coisa, considere várias opções e escolha qual é a melhor para você no momento.
Se você der uma olhada no código - fonte , parece que a opção 3 é o motivo - eles usam esta técnica para ajudar a adicionar funcionalidades / especializar coleções:
fonte
List<T>
é, mas precisa inspecionarStringList
no contexto para realmente entender o que é. Na realidade, é apenasList<string>
, usando um nome diferente. Realmente não parece haver nenhuma vantagem semântica em fazer isso em um caso tão simples. Na verdade, você está apenas substituindo um tipo conhecido pelo mesmo tipo, com um nome diferente.Eu diria que não é uma boa prática.
List<string>
comunica "uma lista genérica de strings". Claramente, osList<string>
métodos de extensão se aplicam. Menos complexidade é necessária para entender; somente a lista é necessária.StringList
comunica "a necessidade de uma classe separada". Talvez osList<string>
métodos de extensão se apliquem (verifique a implementação do tipo real para isso). É necessária mais complexidade para entender o código; A lista e o conhecimento de implementação da classe derivada são necessários.fonte
List<string>
.