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!*/ }
Il existe une autre raison pour laquelle vous souhaiterez peut-être hériter d'un type générique.
Microsoft recommande éviter d'imbriquer des types génériques dans les signatures de méthode .
Il est donc judicieux de créer des types nommés de domaine métier pour certains de vos génériques. Au lieu d'avoir un IEnumerable<IDictionary<string, MyClass>>
, créez un type MyDictionary : IDictionary<string, MyClass>
puis votre membre devient IEnumerable<MyDictionary>
, qui est beaucoup plus lisible. Il vous permet également d'utiliser MyDictionary à plusieurs endroits, augmentant ainsi la justesse de votre code.
Cela peut ne pas sembler être un énorme avantage, mais dans le code des affaires du monde réel, j'ai vu des génériques qui rendront vos cheveux indulgents. Des choses comme List<Tuple<string, Dictionary<int, List<Dictionary<string, List<double>>>>>
. La signification de ce générique n'est en aucun cas évidente. Pourtant, s'il était construit avec une série de types créés explicitement, l'intention du développeur d'origine serait probablement beaucoup plus claire. De plus, si ce type générique devait changer pour une raison quelconque, trouver et remplacer toutes les instances de ce type générique pourrait être une vraie difficulté, par rapport à le changer dans la classe dérivée.
Enfin, il y a une autre raison pour laquelle quelqu'un pourrait souhaiter créer ses propres types dérivés de génériques. Considérez les classes suivantes:
public class MyClass
{
public ICollection<MyItems> MyItems { get; private set; }
// plumbing code here
}
public class MyOtherClass
{
public ICollection<MyItems> MyItemCache { get; private set; }
// plumbing code here
}
public class MyConsumerClass
{
public MyConsumerClass(ICollection<MyItems> myItems)
{
// use the collection
}
}
Maintenant, comment savons-nous quels ICollection<MyItems>
pour passer dans le constructeur de MyConsumerClass? Si nous créons des classes dérivées class MyItems : ICollection<MyItems>
et class MyItemCache : ICollection<MyItems>
, MyConsumerClass peut alors être extrêmement précis sur laquelle des deux collections il veut 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 logique que MyConsumerClass
fonctionne avec n'importe quel ICollection, il peut prendre le constructeur générique. Si, cependant, il n'est jamais judicieux d'utiliser l'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 utile de dériver des types à partir de types génériques - cela permet un code plus explicite le cas échéant et aide à la lisibilité et à la maintenabilité.
En principe, il n'y a pas de différence entre hériter de types génériques et hériter d'autre chose.
Cependant, pourquoi diable dériveriez-vous de la classe any sans rien ajouter de plus?
Je ne connais pas très bien C #, mais je pense que c'est la seule façon d'implémenter un typedef
, que les programmeurs C++ utilisent couramment pour plusieurs raisons:
Ils ont essentiellement rejeté le dernier avantage en choisissant des noms génériques ennuyeux, mais les deux autres raisons sont toujours valables.
Je peux penser à au moins une raison pratique.
La déclaration de types non génériques qui héritent de types génériques (même sans rien ajouter), permet à ces types d'être référencés à partir d'endroits où ils seraient autrement indisponibles ou disponibles d'une manière plus compliquée qu'ils ne devraient l'être.
Un tel cas est lors de l'ajout de paramètres via l'éditeur de paramètres dans Visual Studio, où seuls les types non génériques semblent être disponibles. Si vous y allez, vous remarquerez que tous les System.Collections
les classes sont disponibles, mais aucune collection de System.Collections.Generic
apparaît comme applicable.
Un autre cas est celui de l'instanciation de types via XAML dans WPF. Il n'y a pas de prise en charge pour le passage d'arguments de type dans la syntaxe XML 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.
Je pense que vous devez examiner certains historique.
Sinon, Theodoros Chatzigiannakis a eu les meilleures réponses, par ex. il y a encore beaucoup d'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.
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 la StringList comme paramètre d'entrée d'une méthode, vos appelants ne peuvent pas passer un List<string>
.
En outre, vous devez copier explicitement tous les constructeurs que vous souhaitez utiliser, dans l'exemple StringList, vous ne pouvez pas dire new StringList(new[]{"a", "b", "c"})
, même si List<T>
Définit 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 suis d'accord qu'ils peuvent potentiellement être utiles dans ce cas, mais je les éviterais personnellement même là-bas.
Mais vous ne devriez (à mon avis) jamais les utiliser dans une API publique parce que vous rendez la vie de votre appelant plus difficile ou gaspillez les performances (je n'aime pas vraiment les conversions implicites en général).
Pour ce que ça vaut, 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, et sans même changer le nom de la classe. (J'étais tellement déconcerté par cela 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:
/// <summary>
/// Common base class for C# and VB PE module builder.
/// </summary>
internal abstract class PEModuleBuilder<TCompilation, TSourceModuleSymbol, TAssemblySymbol, TTypeSymbol, TNamedTypeSymbol, TMethodSymbol, TSyntaxNode, TEmbeddedTypesManager, TModuleCompilationState> : CommonPEModuleBuilder, ITokenDeferral
where TCompilation : Compilation
where TSourceModuleSymbol : class, IModuleSymbol
where TAssemblySymbol : class, IAssemblySymbol
where TTypeSymbol : class
where TNamedTypeSymbol : class, TTypeSymbol, Cci.INamespaceTypeDefinition
where TMethodSymbol : class, Cci.IMethodDefinition
where TSyntaxNode : SyntaxNode
where TEmbeddedTypesManager : CommonEmbeddedTypesManager
where TModuleCompilationState : ModuleCompilationState<TNamedTypeSymbol, TMethodSymbol>
{
...
}
Ensuite, dans le projet CSharpCodeanalysis, il y a cette définition:
internal abstract class PEModuleBuilder : PEModuleBuilder<CSharpCompilation, SourceModuleSymbol, AssemblySymbol, TypeSymbol, NamedTypeSymbol, MethodSymbol, SyntaxNode, NoPia.EmbeddedTypesManager, ModuleCompilationState>
{
...
}
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:
Partial Friend MustInherit Class PEModuleBuilder
Inherits PEModuleBuilder(Of VisualBasicCompilation, SourceModuleSymbol, AssemblySymbol, TypeSymbol, NamedTypeSymbol, MethodSymbol, SyntaxNode, NoPia.EmbeddedTypesManager, ModuleCompilationState)
Puisque nous pouvons (espérons-le) supposer que Roslyn a été écrit par des personnes ayant une connaissance approfondie de C # et de la façon dont il devrait être utilisé, je pense que c'est une recommandation de la technique.
Il y a trois raisons possibles pour lesquelles quelqu'un essaierait de faire ça du haut de ma tête:
1) Pour simplifier les déclarations:
Bien sûr, StringList
et ListString
sont à peu près de la même longueur, mais imaginez plutôt que vous travaillez avec un UserCollection
qui est en fait Dictionary<Tuple<string,Type>, IUserData<Dictionary,MySerializer>>
ou un autre grand exemple générique.
2) Pour aider AOT:
Parfois, vous devez utiliser la compilation Ahead Of Time et pas seulement la compilation In Time - par exemple développement pour iOS. Parfois, le compilateur AOT a du mal à déterminer tous les types génériques à l'avance, et cela pourrait être une tentative de lui fournir un indice.
3) Pour ajouter des fonctionnalités ou supprimer des dépendances:
Peut-être qu'ils ont des fonctionnalités spécifiques qu'ils souhaitent implémenter pour StringList
(pourraient être cachés dans une méthode d'extension, pas encore ajoutés ou historiques) qu'ils ne peuvent pas ajouter à List<string>
sans en hériter, ou qu'ils ne veulent pas polluer List<string>
avec.
Alternativement, ils peuvent souhaiter changer l'implémentation de StringList
sur la piste, alors venez d'utiliser la classe comme marqueur pour l'instant (généralement vous feriez/utiliseriez des interfaces pour cela, par exemple IList
).
Je noterais également que vous avez trouvé ce code dans Irony, qui est 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 d'écrire une telle déclaration - même si elle n'est pas trop courante. Comme pour tout, envisagez plusieurs options et choisissez celle qui vous convient le mieux à l'époque.
Si vous regardez le code source il semble que l'option 3 soit la raison - ils utilisent cette technique pour aider à ajouter des collections de fonctionnalité/spécialisation:
public class StringList : List<string> {
public StringList() { }
public StringList(params string[] args) {
AddRange(args);
}
public override string ToString() {
return ToString(" ");
}
public string ToString(string separator) {
return Strings.JoinStrings(separator, this);
}
//Used in sorting suffixes and prefixes; longer strings must come first in sort order
public static int LongerFirst(string x, string y) {
try {//in case any of them is null
if (x.Length > y.Length) return -1;
} catch { }
if (x == y) return 0;
return 1;
}