Is it better to use List<string>
in type annotations or StringList
where StringList
class StringList : List<String> { /* no further code!*/ }
Software Engineering Stack Exchange is a question and answer site for professionals, academics, and students working within the systems development life cycle. It only takes a minute to sign up.
Sign up to join this communityThere is another reason why you may want to inherit from a generic type.
Microsoft recommend avoiding nesting generic types in method signatures.
It is a good idea then, to create business-domain named types for some of your generics. Instead of having a IEnumerable<IDictionary<string, MyClass>>
, create a type MyDictionary : IDictionary<string, MyClass>
and then your member becomes an IEnumerable<MyDictionary>
, which is much more readable. It also allows you to use MyDictionary in a number of places, increasing the explicitness of your code.
This may not sound like a huge benefit, but in real-world business code I've seen generics that will make your hair stand on end. Things like List<Tuple<string, Dictionary<int, List<Dictionary<string, List<double>>>>>
. The meaning of that generic is in no way obvious. Yet, if it were constructed with a series of explicitly created types the intention of the original developer would likely be a lot clearer. Further still, if that generic type needed to change for some reason, finding and replacing all instances of that generic type might be a real pain, compared to changing it in the derived class.
Finally, there is another reason why someone might wish to create their own types derived from generics. Consider the following classes:
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
}
}
Now how do we know which ICollection<MyItems>
to pass into the constructor for MyConsumerClass? If we create derived classes class MyItems : ICollection<MyItems>
and class MyItemCache : ICollection<MyItems>
, MyConsumerClass can then be extremely specific about which of the two collections it actually wants. This then works very nicely with an IoC container, which can resolve the two collections very easily. It also allows the programmer to be more accurate - if it makes sense for MyConsumerClass
to work with any ICollection, they can take the generic constructor. If, however, it never makes business sense to use one of the two derived types, the developer can restrict the constructor parameter to the specific type.
In summary, it is often worthwhile to derive types from generic types - it allows for more explicit code where appropriate and helps readability and maintainability.
In principle there's no difference between inheriting from generic types and inheriting from anything else.
However, why on earth would you derive from any class and not add anything further?
I don't know C# very well, but I believe this is the only way to implement a typedef
, which C++ programmers commonly use for a few reasons:
They basically threw away the last advantage by choosing boring, generic names, but the other two reasons still stand.
StringList
is a List<string>
- they can used interchangeably. This is important when working with other APIs (or when exposing your API), since everyone else in the world uses List<string>
.
using StringList = List<string>;
is the closest C# equivalent of a typedef. Subclassing like this will prevent use of any constructors with arguments.
Dec 17, 2014 at 17:36
using
restricts it to the source code file where the using
is placed. From this point of view, inheritance is closer to typedef
. And creating a subclass does not prevent one from adding all the constructors needed (though it may be tedious).
Dec 17, 2014 at 21:19
using
cannot be used for this purpose, but inheritance can. This shows why I disagree to Pete Kirkham's upvoted comment - I don't consider using
as a real alternative to C++'s typedef
.
Dec 18, 2014 at 8:26
I can think of at least one practical reason.
Declaring non-generic types that inherit from generic types (even without adding anything), allows those types to be referenced from places where they would otherwise be unavailable, or available in a more convoluted way than they should be.
One such case is when adding settings through the Settings editor in Visual Studio, where only non-generic types seem to be available. If you go there, you'll notice that all System.Collections
classes are available, but no collections from System.Collections.Generic
appear as applicable.
Another case is when instantiating types through XAML in WPF. There is no support for passing type arguments in the XML syntax when using a pre-2009 schema. This is made worse by the fact that the 2006 schema seems to be the default for new WPF projects.
I think you need to look into some history.
Otherwise Theodoros Chatzigiannakis had the best answers, e.g. there are still lots of tools that do not understand generic types. Or maybe even a mix of his answer and history.
In some cases it is impossible to use generic types, for example you can't reference a generic type in XAML. In these cases you can create a non-generic type which inherits from the generic type you originally wanted to use.
If possible, I would avoid it.
Unlike a typedef, you create a new type.
If you use the StringList as an input parameter to a method, your callers can't pass a List<string>
.
Also, you would need to explicitly copy all constructors you whish to use, in the StringList example you couldn't say new StringList(new[]{"a", "b", "c"})
, even though List<T>
defines such a constructor.
Edit:
The accepted answer focuses on the pro's when using the derived types inside your domain model. I aggree that they can potentially be useful in this case, still, I personally would avoid them even there.
But you should (in my opinion) never use them in a public API because you either make your caller's life harder or waste performance (I also don't really like implicit conversions in general).
For what it's worth, here's an example of how inheriting from a generic class is used in Microsoft's Roslyn compiler, and without even changing the name of the class. (I was so flummoxed by this that I ended up here in my search to see if this was really possible.)
In project CodeAnalysis you can find this definition:
/// <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>
{
...
}
Then in project CSharpCodeanalysis there's this definition:
internal abstract class PEModuleBuilder : PEModuleBuilder<CSharpCompilation, SourceModuleSymbol, AssemblySymbol, TypeSymbol, NamedTypeSymbol, MethodSymbol, SyntaxNode, NoPia.EmbeddedTypesManager, ModuleCompilationState>
{
...
}
This non-generic PEModuleBuilder class is used extensively in the CSharpCodeanalysis project, and several classes in that project inherit from it, directly or indirectly.
And then in project BasicCodeanalysis there's this definition:
Partial Friend MustInherit Class PEModuleBuilder
Inherits PEModuleBuilder(Of VisualBasicCompilation, SourceModuleSymbol, AssemblySymbol, TypeSymbol, NamedTypeSymbol, MethodSymbol, SyntaxNode, NoPia.EmbeddedTypesManager, ModuleCompilationState)
Since we can (hopefully) assume that Roslyn was written by people with extensive knowledge of C# and how it should be used I'm thinking that this is a recommendation of the technique.
There are three possible reasons why someone would try to do that off the top of my head:
1) To simplify declarations:
Sure StringList
and ListString
are about the same length but imagine instead you're working with a UserCollection
that's actually Dictionary<Tuple<string,Type>, IUserData<Dictionary,MySerializer>>
or some other large generic example.
2) To help AOT:
Sometimes you need to use Ahead Of Time compilation not Just In Time Compilation - e.g. developing for iOS. Sometimes the AOT compiler has difficulty figuring out all the generic types ahead of time, and this could be an attempt to provide it with a hint.
3) To add functionality or remove dependency:
Maybe they have specific functionality that they want to implement for StringList
(could be hidden in an extension method, not added yet, or historical) that they either can't add to List<string>
without inheriting from it, or that they don't want to pollute List<string>
with.
Alternatively they may wish to change the implementation of StringList
down the track, so have just used the class as a marker for now (usually you'd make/use interfaces for this e.g. IList
).
I'd also note that you've found this code in Irony, which is a language parser - a fairly unusual kind of application. There may be some other specific reason that this was used.
These examples demonstrate that there can be legitimate reasons to write such a declaration - even if it's not too common. As with anything consider several options and pick which is best for you at the time.
If you have a look at the source code it appears option 3 is the reason - they use this technique to help add functionality/specialize collections:
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;
}
List<T>
is, but they have to inspect StringList
in context to actually understand what it is. In reality, it's just List<string>
, going by a different name. There really doesn't seem to be any semantic advantage to doing this in such a simple case. You're really just replacing a well-known type with the same type going by a different name.
Dec 18, 2014 at 6:15
I'd say it is not good practice.
List<string>
communicates "a generic list of strings". Clearly List<string>
extension methods apply. Less complexity is required to understand; only List is needed.
StringList
communicates "a need for a separate class". Maybe List<string>
extension methods apply (check the actual type implementation for this). More complexity is needed to understand the code; List and the derived class implementation knowledge is required.
List<string>
.
StringList
andList<string>
? There is none, yet you've (the generic "you") managed to add yet another detail to the code base that is unique only to your project and means nothing to anyone else until after they've studied the code. Why mask the name of a list of strings just to keep calling it a list of strings? On the other hand, if you wanted to doBagOBagels : List<string>
I think that makes more sense. You can iterate it as IEnumerable, external consumers don't care about the implementation--goodness all 'round.