Replace your Collections with IEnumerable<T>
The other day I was remarking on the differences between List<T> and Collection<T>, and how it seemed that List<T> was so much more useful, but officially it shouldn’t be used in public API’s. And what a drag that was, because List<T> has so much good stuff in it.
So, a fellow coworker called me out on this: why am I returning collections other than IEnumerable<T> anyway? What conditions actually require the extensibility of Collection<T>, or the extra utility of List<T>? I didn’t have an answer for this off the top of my head, so over the weekend I created a new experimental branch of a project and started hacking interface signatures. What follows are some conclusions from the experiment.
Setting Up the Experiment
I replaced all the public signatures of IList, Collection, List, etc. with IEnumerable<T>. If it was a public API input or output, its now IEnumerable. Each signature change was followed by re-running all unit tests to make sure that nothing critical broke.
Conclusion: System.Linq makes IEnumerable<T> Useable
There were two major “breaking” changes found in this experiment. The first is that IEnumerable<T> doesn’t expose a Count property, but there are places where we need to know if the list is empty, has one item, has N items, etc.
Fortunately, this codebase was upgraded to Visual Studio 2008 and a .NET 3.5 target a while back. This gave me an opportunity to use the Count() extension method from the System.Linq namespace. First problem: Solved.
Next, there are cases where I’d need to access items in a collection by index. The native IEnumerable doesn’t provide this, but System.Linq does with extensions like First(), and ElementAt().
So, there’s two big reasons for not using IEnumerable<T> taken care of. These changes handled almost all of the collection issues I ran into.
Note: now I’m “stuck” with the .NET 3.5 target. Not a problem for me, YMMV.
[Test] public void GetControls() { // SetUp code omitted for brevity... var cat = new BrowsableUserControlCatalog(); var controls = cat.GetRegisteredUserControls(); // IEnumerable Assert.AreEqual(1, controls.Count()); Assert.AreEqual("virtualPath", controls.First().VirtualPath); }
Conclusion: Sometimes you have to extend Collections
One of the reasons that the Framework Guidlines encourage Collection<T> vs. List<T> is that Collection is easily extended. I ran into one case where a class had been extended from Collection<T> in order to change the behavior of the InsertItem() and SetItem() methods. So we have a collection that requires the ability to add items to it. Even worse, we have methods added to this collection to provide additional required functionality: swap to items in the collection, reset the sorting index of the items, move an item up or down, that sort of thing.
So, that’s one collection that remained as-is. I can live with that. Perhaps on some other weekend, I could split the additional functionality out from that collection and into “something else,” which might have additional advantages. Something for next time..
Conclusion: Sometimes you have to Sort() [for now...]
One List<T> that I haven’t gotten rid of (yet) relies on a Collection<T> supplied by another component. We are getting a list of files, and putting them into a sort order based on what the user interface wants. The library has no idea what kinds of sorting options are available, the calling implementation can do pretty much whatever it wants. The element that varies is the sorting routine passed into List<T>.Sort().
System.Linq provides an .OrderBy() extension, but its signature doesn’t match up with List<T>.Sort()…which turned into more of a change than I wanted to deal with for this current experiment. Saved for next time…
So what have we learned?
Extension methods are fun. I look forward to abusing them heavily in all my new work. Because of them, its now possible to change 99% of this API to use the most general collection types possible.
But does it make sense to do so? I’m not totally convinced. It does simplify things. The semantics of what you can do with a given collection is much clearer. Most of the time, we only needed the abilities of IEnumerable<T>, and it makes sense to use the most generic interface you can get away with – especially for a platform where you can’t predict how these things are going to be used.


