Context: I sent an email to my colleagues telling them about Enumerable.Empty<T>() as a way to return empty collections without doing something like return new List<T>(); I got a reply saying that the downside is that it doesn’t expose a specific type:
That does present a tiny issue. It’s always good to be as specific as possible about your return type* (so if you’re returning a List<>, make that your return type); that’s because things like Lists and Arrays have extra methods that are useful. Plus it also can be useful in making performance considerations when using the collection from the calling method.
The trick below unfortunately forces you to return an IEnumerable, which is about as non-specific as possible, right? 🙁
* This is actually from the .NET Design Guidelines. Stated reasons in the guidelines are the same as I’m mentioning here, I believe.
This seemed to be the complete opposite of what I had learned, and try as I might, I couldn’t find this exact advice in the design guidelines. I did find one small piece like this:
DO return a subclass of
Collection<T>orReadOnlyConnection<T>from very commonly used methods and properties.
With a code snippet following, but no more justification at all.
So that being said, is this a real and accepted guideline (the way it was described in the first block quote)? Or has it been misinterpreted? All other SO questions I could find have answers preferring IEnumerable<T> as the return type. Maybe the original .NET guidelines are just outdated?
Or maybe it’s not so clear cut? Are there some tradeoffs to consider? When would it be a good idea to return a more specific type? Is it ever recommended to return a concrete generic type, or only to return a more specific interface like IList<T> and ReadOnlyCollection<T>?
There are reasons for both styles:
List<T>. Maybe you can promise anIList<T>or a custom collection class (which you can change later).The .NET framework BCL goes with either arrays or custom collection classes. They need to provide a 100% stable API so they need to be careful.
In normal software projects you can change the caller (which the .NET framework guys can’t), so you can be more lenient. If you promise too much, and need to change that a year later, you can do that (with some effort).
There is only one thing that is always wrong: Saying that one should always do (1) or (2). Always is always wrong.
Here is an example for a case where specificity is clearly the right choice:
There is only one reasonable implementation possible, so we can clearly promise that the return type is an array. We will never need to return a list.
On the other hand, a method to return all users from some persistent store might change a lot internally. The data might even be bigger than available memory, which requires streaming. We should probably choose
IEnumerable<User>.