O que é abuso de genéricos?


35

Ao revisar algum código, notei a oportunidade de alterá-lo para usar genéricos. O código (ofuscado) se parece com:

public void DoAllTheThings(Type typeOfTarget, object[] possibleTargets)
{
    var someProperty = typeOfTarget.GetProperty(possibleTargets[0]);
    ...
}

Este código pode ser substituído por genéricos, da seguinte forma:

public void DoAllTheThings<T>(object[] possibleTargets[0])
{
    var someProperty = type(T).getProperty(possibleTargets[0]);
    ...
}

Ao pesquisar os benefícios e deficiências dessa abordagem, encontrei um termo chamado abuso genérico . Vejo:

Minha pergunta vem em duas partes:

  1. Existem benefícios em mudar para genéricos como esse? (Desempenho? Legibilidade?)
  2. O que é abuso de genéricos? E está usando um genérico toda vez que há um parâmetro de tipo um abuso ?

2
Estou confuso. Você conhece o nome da propriedade de antemão, mas não o tipo do objeto, e usa a reflexão para obter um valor?
MetaFight 07/02

3
@MetaFight Eu sei que é um exemplo complicado, o código real seria longo e difícil de colocar aqui. Independentemente disso, o objetivo da minha pergunta é determinar se a substituição do parâmetro Type por um genérico é considerada boa ou ruim.
SyntaxRules

12
Eu sempre considerei essa a razão típica pela qual existem genéricos (trocadilho delicioso não intencional). Por que você mesmo engana os tipos quando pode aproveitar seu sistema de tipos para fazer isso por você?
MetaFight 07/02

2
seu exemplo é estranho, mas eu tenho a sensação de que é exatamente o que um monte de bibliotecas IOD fazer
Ewan

14
Não é uma resposta, mas vale a pena notar que o segundo exemplo é menos flexível que o primeiro. Tornar a função genérica remove sua capacidade de transmitir em um tipo de tempo de execução; Os genéricos precisam saber o tipo em tempo de compilação.
KChaloux

Respostas:


87

Quando os genéricos são aplicados adequadamente, eles removem o código em vez de apenas reorganizá-lo. Principalmente, o código que os genéricos são melhores para remover é tipografia, reflexão e digitação dinâmica. Portanto, o abuso de genéricos pode ser definido livremente como a criação de código genérico sem redução significativa na conversão de tipo, reflexão ou digitação dinâmica em comparação com uma implementação não genérica.

Em relação ao seu exemplo, eu esperaria que um uso apropriado de genéricos alterasse a object[]para a T[]ou similar e evitasse Typeou typecompletamente. Isso pode exigir uma refatoração significativa em outro lugar, mas se o uso de genéricos for apropriado nesse caso, ele deverá terminar mais simples quando terminar.


3
Esse é um ótimo ponto. Admito que o código que me levou a pensar nisso estava realizando uma reflexão difícil de ler. Vou ver se consigo mudar object[]para T[].
SyntaxRules

3
Eu gosto da caracterização remover x reorganizar.
copper.hat

13
Você perdeu um dos principais usos de genéricos - que é reduzir a duplicação. (Embora você possa reduzir a duplicação introduzindo outros pecados mencionados). Se reduzir a duplicação sem remover nenhuma previsão tipográfica, reflexão ou digitação dinâmica IMO, está OK.
Michael Anderson

4
Excelente resposta. Eu acrescentaria que, nesse caso específico, o OP pode precisar mudar para uma interface em vez de genéricos. Parece que a reflexão está sendo usada para acessar propriedades específicas do objeto; uma interface é muito mais adequada para permitir que o compilador determine que essas propriedades realmente existem.
jpmc26

4
@MichaelAnderson "código de remoção em vez de apenas reorganizando-lo" inclui a duplicação reduzindo
Caleth

5

Eu usaria a regra no-nonsense: os genéricos, como todas as outras construções de programação, existem para resolver um problema . Se não houver problema para resolver os genéricos, usá-los é abuso.

No caso específico dos genéricos, eles existem principalmente para se afastar de tipos concretos, permitindo que as implementações de código para os diferentes tipos sejam dobradas em um modelo genérico (ou seja como for que seu idioma o chamar). Agora, suponha que você tenha um código que use um tipo Foo. Você pode substituir esse tipo por um genérico T, mas se você precisar desse código apenas para trabalhar Foo, simplesmente não há outro código com o qual você possa dobrá-lo. Portanto, não existe nenhum problema a ser resolvido; portanto, o indireto de adicionar o genérico serviria apenas para reduzir a legibilidade.

Consequentemente, sugiro apenas escrever o código sem usar genéricos, até que seja necessário apresentá-los (porque você precisa de uma segunda instanciação). Então, e somente então, é a hora de refatorar o código para usar genéricos. Qualquer uso antes desse ponto é abuso aos meus olhos.


Isso parece soar pedante demais, então, lembre-se:
não há regra sem exceções na programação. Esta regra incluída.


Pensamentos interessantes. No entanto, uma pequena reformulação de seus critérios pode ajudar. Suponha que o OP precise de um novo "componente" que mapeie um int para uma string e vice-versa. É muito óbvio que ele resolve uma necessidade comum e pode ser facilmente reutilizável no futuro se tornado genérico. Esse caso se enquadra na sua definição de abuso de genéricos. No entanto, uma vez identificada a necessidade subjacente muito genérica, não valeria a pena investir um esforço adicional insignificante no design sem que isso fosse um abuso?
Christophe

@Christophe Essa é realmente uma pergunta complicada. Sim, há algumas situações em que é realmente melhor ignorar minha regra (não há regra sem exceção na programação!). No entanto, o caso específico de conversão de string está realmente bastante envolvido: 1. Você precisa tratar os tipos de números inteiros assinados e não assinados separadamente. 2. Você precisa tratar as conversões flutuantes separadas das conversões inteiras. 3. Você pode reutilizar uma implementação para os maiores tipos de números inteiros disponíveis para tipos menores. Você pode escrever proativamente um invólucro genérico para fazer a transmissão, mas o núcleo seria sem genéricos.
cmaster

1
Eu argumentaria contra escrever proativamente um YAGNI genérico (pré-uso ao invés de reutilizar) como provável. Quando você precisar, refatorar.
30518 Neil_UK

Ao escrever esta pergunta, adotei mais a postura de "Escreva como um genérico, se possível, para salvar futuras reutilizações possíveis". Eu sabia que isso era um pouco extremo. É refrescante ver o seu ponto de vista aqui. Obrigado!
SyntaxRules

0

O código parece estranho. Mas, se estivermos chamando, parece melhor especificar o tipo como genérico.

https://stackoverflow.com/questions/10955579/passing-just-a-type-as-a-parameter-in-c-sharp

Pessoalmente, não gosto dessas contorções que tornam o código de chamada bonito. por exemplo, a coisa toda 'fluente' e os métodos de extensão.

Mas você tem que admitir que tem seguidores populares. até a microsoft usa-o em unidade, por exemplo

container.RegisterType<IInterface,Concrete>()

0

Para mim, parece que você estava no caminho certo aqui, mas não terminou o trabalho.

Você já pensou em alterar o object[]parâmetro Func<T,object>[]para a próxima etapa?

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.