List<> has no virtual or protected members - such classes should almost never be subclassed. Also, although it's possible you need the full functionality of List<string>
, if you do - is there much point to making such a subclass?
Subclassing has a variety of downsides. If you declare your local type to be FundIdList
, then you won't be able to assign to it by e.g. using linq and .ToList
since your type is more specific. I've seen people decide they need extra functionality in such lists, and then add it to the subclassed list class. This is problematic, because the List implementation ignores such extra bits and may violate your constraints - e.g. if you demand uniqueness and declare a new Add method, anyone that simply (legally) upcasts to List<string>
for instance by passing the list as a parameter typed as such will use the default list Add, not your new Add. You can only add functionality, never remove it - and there are no protected or virtual members that require subclassing to exploit.
So you can't really add any functionality you couldn't with an extension method, and your types aren't fully compatible anymore which limits what you can do with your list.
I prefer declaring a struct FundId
containing a string and implementing whatever guarantees concerning that string you need there, and then working with a List<FundId>
rather than a List<string>
.
Finally, do you really mean List<>
? I see many people use List<>
for things for which IEnumerable<>
or plain arrays are more suitable. Exposing your internal List
in an api is particularly tricky since that means any API user can add/remove/change items. Even if you copy your list first, such a return value is still misleading, since people might expect to be able to add/remove/change items. And if you're not exposing the List
in an API but merely using it for internal bookkeeping, then it's not nearly as interesting to declare and use a type that adds no functionality, only documentation.
Conclusion
Only use List<>
for internals, and don't subclass it if you do. If you want some explicit type-safety, wrap string
in a struct (not a class, since a struct is more efficient here and has better semantics: there's no confusion between a null FundId
and a null string, and object equality and hashcode work as expected with structs but need to be manually specified for classes). Finally, expose IEnumerable<>
if you need to support enumeration, or if you need indexing as well use the simple ReadOnlyCollection<>
wrapper around your list rather than let the API client fiddle with internal bits. If you really need a mutatable list API, ObservableCollection<>
at least lets you react to changes the client makes.