36

Is it better to use List<string> in type annotations or StringList where StringList

class StringList : List<String> { /* no further code!*/ }

I ran into several of these in Irony.

MetaFight
  • 11,549
  • 3
  • 44
  • 75
Ruudjah
  • 558
  • 1
  • 5
  • 11
  • If you don't inherit from generic types, then why are they there? – Mawg says reinstate Monica Dec 17 '14 at 15:08
  • 7
    @Mawg They can be available only for instantiation. – Theodoros Chatzigiannakis Dec 17 '14 at 15:24
  • Produced a lot of code like this when upgrading code from .NET 1 style collections. – Joshua Dec 17 '14 at 23:02
  • 4
    Yuck. No, seriously. What's the semantic difference between `StringList` and `List`? 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 do `BagOBagels : List` I think that makes more sense. You can iterate it as IEnumerable, external consumers don't care about the implementation--goodness all 'round. – Craig Tullis Dec 18 '14 at 05:59
  • 1
    XAML has an issue where you can't use generics and you needed to make a type like this to populate a list – Daniel Little Dec 18 '14 at 05:59
  • [Related](http://programmers.stackexchange.com/q/246277/72131) – user11153 Dec 18 '14 at 10:07
  • @Craig I might see it a use of it in Java as a workaround to type erasure by the compiler... but in C# yeah, totally agree, I dont see the point of it either. – Newtopian Feb 14 '18 at 13:30
  • That’s what you get for using Java. :p – Craig Tullis Feb 14 '18 at 16:46
  • I was thinking of doing something like this for classes like `Vector3`. I was thinking of having a `Vector3Base` which is extended to `Vector3` (precision determined by compiler flags), `Vector3f`, `Vector3d` (constant precision), etc. Good idea or bad idea? – Aaron Franke Feb 27 '18 at 21:28

9 Answers9

37

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?

Julia Hayward
  • 2,872
  • 2
  • 15
  • 12
  • 18
    I agree. Without contributing anything, I'd read "StringList" and think to myself, "What does it *really* do?" – Neil Dec 17 '14 at 11:05
  • 1
    I agree as well, in may language to inherit you use the key word "extends", this is a good reminder that if you're going to inherit a class then you should be extending it with further functionality. – Elliot Blackburn Dec 17 '14 at 16:01
  • 15
    -1, for not understanding that this is just one way for creating an abstraction by choosing a different name - creating an abstractions can be a very good reason for not adding anything to this class. – Doc Brown Dec 17 '14 at 21:27
  • To me it could make sense to inherit something without adding any functionatlity. It's a way to make a shorthand for something. I could make `List` into `TypeList` or something. – BWG Dec 17 '14 at 22:38
  • 1
    Consider my answer, I go into some of the reasons someone might decide to do this. – NPSF3000 Dec 17 '14 at 22:46
  • If a type extends another without adding anything, references of the latter type will only be able to identify instances of that type. For example, if one were to define `IImmutableEnumerable : IEnumerable`, and all immutable enumerable types one was interested implemented that interface, then code which wants an enumerable sequence which can be guaranteed not to always return the same sequence of values could use a parameter of type `IImmutableEnumerable`. Such an interface wouldn't add any new *members*, but that wouldn't prevent it from making new *promises*. – supercat Dec 17 '14 at 23:04
  • @BWG But in that (or any similar) you've surely only saved two (`<>`) characters? It would be not `TypeList`, but `BigHonkingTypeNameList`, surely? – OJFord Dec 18 '14 at 03:50
  • @OllieFord In this particular example, it's kind of dumb. But it's not stupid for longer types, and it might have been to maintain a consistent style. – BWG Dec 18 '14 at 03:55
  • 1
    "why on earth would you derive from any class and not add anything further?" I've done it for user controls. You can create a generic user control but you can't use it in a windows form designer, so you have to inherit from it, specifying the type, and use that one. – George T Dec 18 '14 at 10:34
28

There 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.

Stephen
  • 8,800
  • 3
  • 30
  • 43
  • 12
    That's an absolutely ridiculous Microsoft recommendation. – Thomas Eding Dec 20 '14 at 15:39
  • 9
    I don't think it's that ridiculous for public APIs. Nested generics are difficult to understand at a glance and a more meaningful type name can often aid readability. – Stephen Jan 04 '15 at 22:52
  • 5
    I don't think it's ridiculous, and it's certainly okay for private APIs... but for public APIs I don't think it's a good idea. Even Microsoft recommend accepting and returning the basest of types when you're writing APIs meant for public consumption. – Paul d'Aoust Feb 20 '15 at 17:49
13

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:

  • It keeps your code from looking like a sea of angle brackets.
  • It lets you swap out different underlying containers if your needs change later, and makes it clear you reserve the right to do so.
  • It lets you give a type a name that is more relevant in context.

They basically threw away the last advantage by choosing boring, generic names, but the other two reasons still stand.

Karl Bielefeldt
  • 146,727
  • 38
  • 279
  • 479
  • 7
    Eh... They're not quite the same. In C++ you have a literal alias. `StringList` **is** a `List` - 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`. – Telastyn Dec 17 '14 at 14:06
  • 2
    I realize they're not exactly the same thing. Just as close as is available. There are certainly drawbacks to the weaker version. – Karl Bielefeldt Dec 17 '14 at 14:42
  • +1. As has been pointed out, this is not quite the same as typedef, but it still is very useful in some scenarios, because it helps the reader distinguish between pieces of data that are of the same type but shouldn't be mixed and pieces of data that are of the same type and should be mixed. – Theodoros Chatzigiannakis Dec 17 '14 at 15:22
  • 24
    No, `using StringList = List;` is the closest C# equivalent of a typedef. Subclassing like this will prevent use of any constructors with arguments. – Pete Kirkham Dec 17 '14 at 17:36
  • 4
    @PeteKirkham: defining a StringList by `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). – Doc Brown Dec 17 '14 at 21:19
  • @DocBrown typedefs are also limited to the source code file they're in, technically. Though C# not having includes makes this more limited than in C++. – Random832 Dec 17 '14 at 21:21
  • @Random832 Ehmm, all non native types in C++ are limited to the source file it's written in - that's why you cannot compare with C# without considering #include. – Petter Nordlander Dec 18 '14 at 01:14
  • 2
    @Random832: let me express this differently: in C++, a typedef can be used to assign the name *in one place*, to make it a "single source of truth". In C#, `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`. – Doc Brown Dec 18 '14 at 08:26
  • @DocBrown then there isn't a closest approximation - if you think scope is more important than semantics, then inheriting and hiding all constructors is closer; if you think semantics is more important, the semantics of inheritance is 'this is a sub-type' not 'this is an alias'. Also you cannot use inheritance to 'rename' a sealed type, and many systems adopt the open-closed principle by requiring all public classes to be either sealed or abstract. – Pete Kirkham Dec 19 '14 at 11:13
9

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.

  • 1
    In WCF web service interfaces you can use this technique to provide better looking collection type names (eg. PersonCollection instead of ArrayOfPerson or whatever) – Rico Suter Dec 18 '14 at 09:51
7

I think you need to look into some history.

  • C# has been used for a lot longer than it has had generic types for.
  • I expect that the given software had its own StringList at some point in history.
  • This may well have been implemented by wrapping an ArrayList.
  • Then someone a to refactoring to move the code base forward.
  • But did not wish to touch 1001 different files, so finish the job.

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.

Ian
  • 4,594
  • 18
  • 28
  • 3
    "C# has been used for a lot longer than it has had generic types for." Not really, C# without generics existed for about 4 years (January 2002 - November 2005), while C# with generics is now 9 years old. – svick Dec 18 '14 at 15:01
  • Also, [Irony used these non-generic classes inheriting from generic ones since the first commit in 2007](https://github.com/Alxandr/Irony/blob/3b31392762387af9b7fad77d089305ad63c52ed2/Irony/Compiler/AST/AstNode.cs#L20). – svick Dec 18 '14 at 15:05
4

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).

Lukas Rieger
  • 231
  • 1
  • 6
  • 3
    You say, "*you can't reference a generic type in XAML*", but I'm not sure what you're referring to. See [Generics in XAML](http://msdn.microsoft.com/en-us/library/ee956431%28v=vs.110%29.aspx) – p.s.w.g Dec 17 '14 at 16:28
  • 1
    It was meant as an example. Yes, it is possible with the 2009 XAML Schema, but AFAIK not with the 2006 Schema, which is still the VS default. – Lukas Rieger Dec 18 '14 at 00:03
  • 1
    Your third paragraph is just half true, you can actually allow this with implicit converters ;) – Knerd Dec 22 '14 at 18:55
  • 1
    @Knerd, true, but then you 'implicitly' create a new object. Unless you do a lot more work, this will also create a copy of the data. Probably doesn't matter with small lists, but have fun, if someone passes a list with a few thousand elements =) – Lukas Rieger Dec 22 '14 at 19:05
  • @LukasRieger you are right :P I just wanted to point that out :P – Knerd Dec 23 '14 at 10:26
2

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.

RenniePet
  • 147
  • 5
1

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; 
    }
NPSF3000
  • 285
  • 1
  • 5
  • 1
    Sometimes the quest to simplify declarations (or simplify whatever) can result in increased complexity, rather than reduced complexity. Everybody who knows the language moderately well knows what a `List` is, but they have to inspect `StringList` in context to actually understand what *it* is. In reality, it's just `List`, 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. – Craig Tullis Dec 18 '14 at 06:15
  • @Craig I agree, as noted by my explanation of usecase (longer declarations) and other possible reasons. – NPSF3000 Dec 18 '14 at 06:32
-1

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.

Ruudjah
  • 558
  • 1
  • 5
  • 11
  • 4
    Extension methods apply anyway. – Theodoros Chatzigiannakis Dec 17 '14 at 15:29
  • 2
    Off course. But you don't know that untill you inspect the StringList and see it derives from `List`. – Ruudjah Dec 18 '14 at 15:26
  • @TheodorosChatzigiannakis I wonder if Ruudjah could elaborate on what he means by "extension methods don't apply." Extension methods are a possibility with *any* class. Sure, the .NET framework library ships with a lot of extension methods gratuitously called LINQ that apply to the generic collection classes, but that doesn't mean extension methods don't apply to any other classes? Maybe Ruudjah is making a point that isn't obvious at first glance? ;-) – Craig Tullis Dec 19 '14 at 05:38
  • "extension methods don't apply." Where do you read this? I'm saying "extension methods *may* apply. – Ruudjah Dec 19 '14 at 08:40
  • I probably misunderstood, then. – Theodoros Chatzigiannakis Dec 19 '14 at 09:07
  • 1
    The type implementation won't tell you whether or not extension methods apply, though, right? Just the fact that it's a class means extension methods *do* apply. Any consumer of your type can create extension methods completely independently of your code. I guess that's what I'm getting at or asking. Are you just talking about LINQ? LINQ is *implemented* using extension methods. But the absence of LINQ-specific extension methods for a particular type doesn't mean that extension methods for that type don't exist, or won't. They could be created at any time by anyone. – Craig Tullis Dec 21 '14 at 07:11
  • Seeing `StringList` does not tell you if `List` extension methods apply. That's why "extension methods *may* apply". You have to look at the class declaration of StringList to know for sure. I maybe should have phrased as "**`List`** extension methods *may* apply" – Ruudjah Dec 22 '14 at 09:34