Qu'est-ce que l'abus de génériques?

35

En passant en revue du code, j'ai remarqué qu'il était possible de le changer pour qu'il utilise des génériques. Le code (obscurci) ressemble à:

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

Ce code pourrait être remplacé par des génériques, comme ceci:

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

En recherchant les avantages et les inconvénients de cette approche, j'ai trouvé un terme appelé abus générique . Voir:

Ma question est en deux parties:

  1. Y at-il des avantages à passer à des génériques comme celui-ci? (Performance? Lisibilité?)
  2. Qu'est-ce que l'abus de génériques? Et l'utilisation d'un générique à chaque fois qu'il y a un paramètre de type est-elle un abus ?
SyntaxRules
la source
2
Je suis confus. Vous connaissez le nom de la propriété à l’avance, mais vous ne connaissez pas le type de l’objet, vous utilisez donc la réflexion pour obtenir une valeur?
MetaFight
3
@MetaFight Je sais que c'est un exemple compliqué, le code réel serait trop long et difficile à mettre ici. Quoi qu’il en soit, le but de ma question est de déterminer si le remplacement du paramètre Type par un générique est considéré comme une forme bonne ou mauvaise.
SyntaxRules
12
J'ai toujours considéré cela comme la raison typique de l'existence des génériques (jeu de mots délicieux non prévu). Pourquoi vous trompez vous-même lorsque vous pouvez utiliser votre système de saisie pour le faire à votre place?
MetaFight
2
votre exemple est étrange, mais j'ai le sentiment que c'est exactement ce que font beaucoup de bibliothèques IoD
Ewan
14
Pas une réponse, mais il convient de noter que le deuxième exemple est moins flexible que le premier. Rendre la fonction générique supprime votre capacité à passer dans un type d'exécution; Les génériques ont besoin de connaître le type au moment de la compilation.
KChaloux

Réponses:

87

Lorsque les génériques sont correctement appliqués, ils suppriment le code au lieu de le réorganiser. Principalement, le code que les génériques sont les mieux à même de supprimer est la typographie, la réflexion et le typage dynamique. Par conséquent, l’abus des génériques pourrait être défini de manière vague comme la création d’un code générique sans réduction significative de la conversion de type, de la réflexion ou du typage dynamique par rapport à une implémentation non générique.

En ce qui concerne votre exemple, je me attends à une utilisation appropriée des médicaments génériques pour changer l' object[]un T[]ou similaire, et éviter Typeou typetout à fait. Cela nécessitera peut-être une refactorisation importante ailleurs, mais si l'utilisation de génériques est appropriée dans ce cas, son utilisation devrait être plus simple lorsque vous avez terminé.

Karl Bielefeldt
la source
3
C'est un bon point. J'admets que le code qui m'a fait penser à cela rendait la réflexion difficile à lire. Je verrai si je peux changer object[]pour T[].
SyntaxRules
3
J'aime la caractérisation supprimer / réorganiser.
copper.hat
13
Vous avez manqué une utilisation clé des génériques - qui consiste à réduire les doubles emplois. (Bien que vous puissiez réduire les doubles emplois en introduisant l’un de ces autres péchés que vous mentionnez). Si cela réduit la duplication sans supprimer aucune typecasts, réflexion ou typage dynamique IMO, tout va bien.
Michael Anderson
4
Excellente réponse. J'ajouterais que dans ce cas particulier, l'OP peut avoir besoin de passer à une interface plutôt qu'à des génériques. Il semble que la réflexion soit utilisée pour accéder à des propriétés particulières de l'objet. une interface est bien mieux adaptée pour permettre au compilateur de déterminer que de telles propriétés existent réellement.
Jpmc26
4
@MichaelAnderson "supprimer le code au lieu de le réorganiser" inclut la réduction des duplications
Caleth
5

J'utiliserais la règle «non-nonsense»: les génériques, comme toutes les autres constructions de programmation, existent pour résoudre un problème . S'il n'y a pas de problème à résoudre pour les génériques, les utiliser est un abus.

Dans le cas spécifique des génériques, ils existent généralement pour résumer les types concrets, ce qui permet aux implémentations de code pour les différents types d'être regroupées dans un modèle générique (ou quel que soit le langage utilisé par votre langage). Supposons maintenant que votre code utilise un type Foo. Vous pouvez remplacer ce type par un générique T, mais si vous n’avez besoin que de ce code Foo, il n’ya tout simplement aucun autre code avec lequel vous pouvez le plier. Ainsi, il n’ya pas de problème à résoudre, le simple fait d’ajouter le générique ne servirait donc qu’à réduire la lisibilité.

Par conséquent, je suggérerais d'écrire simplement le code sans utiliser de génériques, jusqu'à ce que vous voyiez la nécessité de les introduire (car vous avez besoin d'une seconde instanciation). Alors, et alors seulement, le temps est venu de refactoriser le code pour utiliser des génériques. Toute utilisation avant ce point est un abus à mes yeux.


Cela semble un peu trop pédant, alors laissez-moi vous rappeler:
Il n’ya pas de règle sans exception en programmation. Cette règle inclus.

cmaster
la source
Pensées intéressantes. Cependant, une légère reformulation de vos critères pourrait vous aider. Supposons que OP ait besoin d'un nouveau "composant" qui mappe un int sur une chaîne et inversement. Il est très évident que cela répond à un besoin commun et pourrait être facilement réutilisé à l'avenir s'il était générique. Ce cas tomberait dans votre définition de l'abus de génériques. Cependant, une fois le besoin sous-jacent générique identifié, ne vaudrait-il pas la peine d'investir un effort supplémentaire insignifiant dans la conception sans que cela soit un abus?
Christophe
@ Christophe C'est en effet une question délicate. Oui, il y a des situations où il est en effet préférable d'ignorer ma règle (il n'y a pas de règle sans exception en programmation!). Toutefois, le cas spécifique de conversion de chaîne est plutôt complexe: 1. Vous devez traiter les types entiers signés et non signés séparément. 2. Vous devez traiter les conversions de type float séparément des conversions de nombre entier. 3. Vous pouvez réutiliser une implémentation pour les types d'entiers disponibles les plus grands pour les types plus petits. Vous voudrez peut-être écrire de manière proactive un wrapper générique pour faire le casting, mais le noyau serait sans génériques.
cmaster
1
Je m'opposerais à l'écriture proactive d'un générique (pré-utilisation plutôt que réutilisation) comme YAGNI. Quand vous en avez besoin, alors refactor.
Neil_UK
En rédigeant cette question, j’ai adopté la position suivante: "Si possible, écrivez-le en tant que générique pour éviter une éventuelle réutilisation future." Je savais que c'était un peu extrême. Il est rafraîchissant de voir votre point de vue ici. Merci!
SyntaxRules
0

Le code semble étrange. Mais si nous l’appelons, il semble plus pratique de spécifier le type en tant que générique.

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

Personnellement, je n'aime pas ces contorsions qui rendent le code d'appel joli. par exemple, tout ce qui est "fluent" et les méthodes d'extension.

Mais vous devez admettre qu'il a un public populaire. même Microsoft l'utiliser dans l'unité par exemple

container.RegisterType<IInterface,Concrete>()
Ewan
la source
0

Pour moi, on dirait que vous êtes sur le bon chemin ici, mais que vous n'avez pas fini le travail.

Avez-vous envisagé de changer le object[]paramètre Func<T,object>[]pour la prochaine étape?

sq33G
la source