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.

kick it on DotNetKicks.com

9 Responses to “Replace your Collections with IEnumerable<T>”

  1. I’ve been returning arrays and accepting IEnumerable as parameter. This eliminates the need for System.Linq in order to do something with the collection. Thoughts?

  2. Mostly, its just a matter of personal style.

    There is an FxCop rule warning about returning arrays from properties, the issue being that an outside caller gets a reference to your array, and can alter it and therefore alter the state of your class: http://msdn2.microsoft.com/en-us/library/0fss9skc.aspx

    For methods: an array implements IList, so returning an array is really just a step or so away from my original starting place of returning Collection or List. Returning a more abstract type than an array gives you more options for extensibility - you could replace the array with another collection type that had more implementation. This is only a factor if you’ve got developers out there “in the cloud” who rely on your return type - if its not a big deal to make breaking changes, then it doesn’t matter so much.

  3. If you’re passing around a collection where you know you’ll be frequently accessing the count, I’d argue that you should just be returning ICollection. Do you want to be able to index into it? IList. The extension methods can be nice where someone returned you an IEnumerable, and it turns out that you want to know the count. Internally, it’ll just call the Count() method if it’s an ICollection underneath. Otherwise, it’ll enumerate through them to get a count.

    All that being said, I’ve personally refactored some code to move to IEnumerable where I *knew* I’d be accessing a Count at one point, but it was a Count that needed to disappear at some point, so I was okay with it. :)

  4. Okay… it removed all of my nice generics markup… argh. Before everyone starts yelling at me for not using generics, I was referring to ICollection(T), IList(T), and IEnumerable(T).

  5. I’ve been doing this for a while now. You should always try to pass the most basic object you can to between layers.

    But something I have been wondering about these days, when I’m passing data between layers (say between a view and a presenter), do you just stick with passing primitives, or do you create a specialized data class?

    If you stick with primitives you end up with a more complex interface between the objects. Also, if you create a special data object you can keep the data interaction better in mind for what is intended to happen. On the down side, you now have another tiny class to manage.

  6. [...] Replace your Collections with IEnumerable<T> - Tony Rasa looks at moving from concrete collection classes in his API to IEnumerable<T> [...]

  7. @trasa I didn’t mean from properties. and yes, arrays implement IList but IList.Add? Please don’t try that on your array. In my experience it’s easiest for the consumer to just handle the array, new List(tArr); .. but I could be swayed. I’ll play with it.

  8. The main reason I can see for moving a method that returns a Collection or List to returning an IEnumerable is that you then have the use of the yield keyword.

    http://flimflan.com/blog/ThePowerOfYieldReturn.aspx

    Joshua is better at explaining things than I am.

  9. [...] Replace your Collections with IEnumerable ???LinQ???? [...]

Leave a Reply

Close
E-mail It