Est-il préférable d'utiliser List<string>
dans les annotations de type ou StringList
oùStringList
class StringList : List<String> { /* no further code!*/ }
c#
inheritance
Ruudjah
la source
la source
StringList
etList<string>
? Il n'y en a pas, et pourtant vous avez (le "générique" vous) réussi à ajouter encore un autre détail à la base de code qui est unique à votre projet et qui ne signifie rien à personne d'autre qu'après avoir étudié le code. Pourquoi masquer le nom d'une liste de chaînes simplement pour continuer à l'appeler liste de chaînes? D'autre part, si vous vouliez le faire,BagOBagels : List<string>
je pense que cela a plus de sens. Vous pouvez le parcourir de la même manière que IEnumerable, les consommateurs externes ne se soucient pas de la mise en œuvre - du bien à tout prix.Réponses:
Il y a une autre raison pour laquelle vous pouvez souhaiter hériter d'un type générique.
Microsoft recommande d' éviter l'imbrication de types génériques dans les signatures de méthodes .
Il est donc judicieux de créer des types nommés business-domain pour certains de vos génériques. Au lieu d’avoir un
IEnumerable<IDictionary<string, MyClass>>
, créez un typeMyDictionary : IDictionary<string, MyClass>
, puis votre membre devient unIEnumerable<MyDictionary>
, ce qui est beaucoup plus lisible. Il vous permet également d’utiliser MyDictionary à plusieurs endroits, augmentant ainsi le caractère explicite de votre code.Cela peut ne pas sembler être un avantage énorme, mais dans le code du monde des affaires, j'ai vu des génériques qui feraient dresser vos cheveux. Des choses comme
List<Tuple<string, Dictionary<int, List<Dictionary<string, List<double>>>>>
. La signification de ce générique n’est nullement évidente. Cependant, s'il était construit avec une série de types explicitement créés, l'intention du développeur d'origine serait probablement beaucoup plus claire. En outre, si ce type générique devait changer pour une raison quelconque, la recherche et le remplacement de toutes les instances de ce type générique pourraient être un véritable casse-tête, par rapport à sa modification dans la classe dérivée.Enfin, il existe une autre raison pour laquelle une personne peut souhaiter créer ses propres types dérivés de génériques. Considérez les classes suivantes:
Maintenant, comment savons-nous qui
ICollection<MyItems>
doit passer dans le constructeur de MyConsumerClass? Si nous créons des classes dérivéesclass MyItems : ICollection<MyItems>
et queclass MyItemCache : ICollection<MyItems>
MyConsumerClass peut alors être extrêmement spécifique sur celle des deux collections qu’elle souhaite réellement. Cela fonctionne alors très bien avec un conteneur IoC, qui peut résoudre les deux collections très facilement. Cela permet également au programmeur d’être plus précis: s’il est judicieuxMyConsumerClass
de travailler avec n'importe quel ICollection, ils peuvent utiliser le constructeur générique. Si, toutefois, il n’est jamais logique d’utiliser un des deux types dérivés, le développeur peut restreindre le paramètre constructeur au type spécifique.En résumé, il est souvent intéressant de dériver des types à partir de types génériques - cela permet un code plus explicite, le cas échéant, et facilite la lisibilité et la maintenabilité.
la source
En principe, il n'y a pas de différence entre hériter de types génériques et hériter de rien d'autre.
Cependant, pourquoi diable tireriez-vous de n'importe quelle classe sans rien ajouter de plus?
la source
Je ne connais pas très bien le langage C #, mais je pense que c’est le seul moyen d’implémenter un
typedef
logiciel couramment utilisé par les programmeurs C ++ pour plusieurs raisons:Ils ont essentiellement rejeté le dernier avantage en choisissant des noms génériques ennuyeux, mais les deux autres raisons sont valables.
la source
StringList
est unList<string>
- ils peuvent être utilisés de manière interchangeable. Ceci est important lorsque vous travaillez avec d'autres API (ou lorsque vous exposez votre API), car tous les autres utilisateurs du monde l'utilisentList<string>
.using StringList = List<string>;
est l'équivalent C # le plus proche d'un typedef. Ce sous-classement empêchera l'utilisation de constructeurs avec des arguments.using
limitant au fichier de code source oùusing
est placé le. De ce point de vue, l'héritage est plus proche detypedef
. Et créer une sous-classe n’empêche pas d’ajouter tous les constructeurs nécessaires (bien que cela puisse être fastidieux).using
ne peut pas être utilisé à cette fin, mais l'héritage le peut. Cela montre pourquoi je ne suis pas d'accord avec le commentaire voté de Pete Kirkham - je ne considère pasusing
comme une véritable alternative au C ++typedef
.Je peux penser à au moins une raison pratique.
La déclaration de types non génériques héritant de types génériques (même sans rien ajouter) permet de référencer ces types à partir d’endroits où ils ne seraient pas disponibles ou disponibles de manière plus compliquée qu’ils ne le devraient.
C'est le cas, par exemple, lorsque vous ajoutez des paramètres via l'éditeur de paramètres dans Visual Studio, où seuls les types non génériques semblent disponibles. Si vous y allez, vous remarquerez que toutes les
System.Collections
classes sont disponibles, mais aucune collectionSystem.Collections.Generic
n'apparaît comme applicable.Un autre cas concerne l'instanciation de types via XAML dans WPF. La prise en charge des arguments de type dans la syntaxe XML n'est pas prise en charge lors de l'utilisation d'un schéma antérieur à 2009. Cela est aggravé par le fait que le schéma de 2006 semble être la valeur par défaut pour les nouveaux projets WPF.
la source
Je pense que vous devez regarder dans une histoire .
Sinon, Theodoros Chatzigiannakis avait les meilleures réponses. Par exemple, il existe encore de nombreux outils qui ne comprennent pas les types génériques. Ou peut-être même un mélange de sa réponse et de son histoire.
la source
Dans certains cas, il est impossible d'utiliser des types génériques, par exemple, vous ne pouvez pas référencer un type générique dans XAML. Dans ces cas, vous pouvez créer un type non générique qui hérite du type générique que vous vouliez utiliser à l'origine.
Si possible, je l'éviterais.
Contrairement à un typedef, vous créez un nouveau type. Si vous utilisez StringList en tant que paramètre d'entrée pour une méthode, vos appelants ne peuvent pas passer un
List<string>
.En outre, vous devez explicitement copier tous les constructeurs que vous souhaitez utiliser. Dans l'exemple StringList, vous ne pouvez pas le dire
new StringList(new[]{"a", "b", "c"})
, même si vousList<T>
définissez un tel constructeur.Modifier:
La réponse acceptée se concentre sur les professionnels lors de l'utilisation des types dérivés dans votre modèle de domaine. Je conviens qu'ils peuvent potentiellement être utiles dans ce cas, mais personnellement, je les éviterais même là.
Mais vous devriez (à mon avis) ne jamais les utiliser dans une API publique, soit parce que vous rendez la vie de votre appelant plus difficile ou que vous gaspillez les performances (je n'aime pas vraiment les conversions implicites en général).
la source
Voici un exemple de la façon dont l'héritage d'une classe générique est utilisé dans le compilateur Roslyn de Microsoft, sans même changer le nom de la classe. (J'étais tellement décontenancé par ça que je me suis retrouvé ici dans ma recherche pour voir si c'était vraiment possible.)
Dans le projet CodeAnalysis, vous pouvez trouver cette définition:
Ensuite, dans le projet CSharpCodeanalysis, il y a cette définition:
Cette classe PEModuleBuilder non générique est largement utilisée dans le projet CSharpCodeanalysis et plusieurs classes de ce projet en héritent directement ou indirectement.
Et puis dans le projet BasicCodeanalysis, il y a cette définition:
Puisque nous pouvons (espérons-le) supposer que Roslyn a été écrit par des personnes ayant une connaissance approfondie de C # et de la manière dont il devrait être utilisé, je pense qu'il s'agit d'une recommandation de la technique.
la source
Il y a trois raisons possibles pour lesquelles quelqu'un essaierait de le faire par hasard:
1) Pour simplifier les déclarations:
Bien sûr ,
StringList
etListString
sont sur la même longueur mais imaginez au lieu que vous travaillez avec unUserCollection
qui est en faitDictionary<Tuple<string,Type>, IUserData<Dictionary,MySerializer>>
ou d' un autre grand exemple générique.2) Pour aider AOT:
Parfois, vous devez utiliser la compilation Ahead Of Time et non la compilation Just In Time - par exemple, le développement pour iOS. Parfois, le compilateur AOT a du mal à comprendre tous les types génériques à l’avance, ce qui peut être une tentative de lui donner un indice.
3) Ajouter des fonctionnalités ou supprimer des dépendances:
Peut-être qu'ils ont des fonctionnalités spécifiques qu'ils souhaitent mettre en œuvre pour
StringList
(peut être caché dans une méthode d'extension, pas encore ajouté ou historique) qu'ils soit ne peuvent pas ajouterList<string>
sans hériter de lui, ou qu'ils ne veulent pas polluerList<string>
avec .Alternativement, ils peuvent souhaiter changer l’implémentation de
StringList
la piste, alors venez d’utiliser la classe comme marqueur (pour l’instant, vous feriez / utiliseriez des interfacesIList
).Je tiens également à noter que vous avez trouvé ce code dans Irony, un analyseur de langage, un type d'application assez inhabituel. Il peut y avoir une autre raison spécifique pour laquelle cela a été utilisé.
Ces exemples démontrent qu'il peut y avoir des raisons légitimes pour écrire une telle déclaration - même si elle n'est pas trop courante. Comme pour toute chose, envisagez plusieurs options et choisissez celle qui vous convient le mieux à ce moment-là.
Si vous examinez le code source, il apparaît que l'option 3 en est la raison - ils utilisent cette technique pour aider à ajouter des fonctionnalités / spécialiser des collections:
la source
List<T>
, mais ils doivent inspecterStringList
dans le contexte pour réellement comprendre ce que c’est . En réalité, c'est justeList<string>
, sous un nom différent. Il ne semble vraiment pas y avoir d’avantage sémantique à le faire dans un cas aussi simple. En fait, vous remplacez simplement un type bien connu par le même type sous un nom différent.Je dirais que ce n'est pas une bonne pratique.
List<string>
communique "une liste générique de chaînes". LesList<string>
méthodes d'extension s'appliquent clairement . Moins de complexité est nécessaire pour comprendre; seule la liste est nécessaire.StringList
communique "le besoin d'une classe séparée". Peut êtreList<string>
méthodes d’extension s’appliquent (vérifiez l’implémentation du type réel). Plus de complexité est nécessaire pour comprendre le code; Une connaissance de la liste et de la mise en œuvre de classe dérivée est requisela source
List<string>
.