IEnumerable vs ICollection vs IList

Paul Burgess
4 min readJan 18, 2018
The interfaces List<T> implements

I was listening to an episode of Coding Blocks (an excellent podcast for programmers, I’d recommend checking them out at https://www.codingblocks.net/) and they were talking about implementing interfaces, and specifically how complicated it is to implement IList<T>. All they really wanted was to create their own list where they could add and remove items from, but IList<T> required a lot more methods, and was overkill for what they wanted.

The overall point of this discussion was to keep interfaces simple, and implement multiple interfaces, rather than create lots of large interfaces. This is an excellent piece of advice, and one that has helped me many times. However what they failed to mention was there was probably an interface in between IEnumerable<T> and IList<T> that would have served their needs. That is ICollection<T> and I use it all the time. In this article I’m going to explore the difference between the three, and their purpose.

List<T>

I thought I would start by looking at List<T>. Most people will use List<T> in a lot of their methods as parameters. Over on the .NET documentation, the signature for List<T> is the following:

public class List<T> : System.Collections.Generic.ICollection<T>, System.Collections.Generic.IEnumerable<T>, System.Collections.Generic.IList<T>, System.Collections.Generic.IReadOnlyCollection<T>, System.Collections.Generic.IReadOnlyList<T>, System.Collections.IList

That is a lot of interfaces implemented! So what do they all do? And can we perhaps use some of them instead of List<T> to make our methods more flexible?

IEnumerable<T>

The most basic of the interfaces implemented. Lets see what IEnumerable<T> looks like:

IEnumerator<T> GetEnumerator(); //Returns an enumerator that iterates through the collection.

One method, GetEnumerator(). All an IEnumerable<T> lets us do is enumerate over our enumerable. For something so simple, this enables a lot of functionality. For a start, it gives us a whole range of extension methods, in fact the entire LINQ library.

The Enumerable pattern and specifically IEnumerator was stated in the book Design Patterns (Gamma et al) as the Iterator pattern. It defines a very simple way to iterate over a collection. IEnumerator only has three methods

T Current() //Gets the element in the collection at the current position of the enumerator.void MoveNext() //Advances the enumerator to the next element of the collection.void Reset() //Sets the enumerator to its initial position, which is before the first element in the collection.

These three simple methods lets us traverse an entire collection, getting each element. However the collection can also be infinite, as there is nothing on the IEnumerable<T> interface to enforce the collection having to have a start and end. It’s important to remember this, because depending on what enumerable you are iterating over, you could potentially create an infinite loop if you don’t know whether your source could be infinite or not.

ICollection<T>

So IEnumerable<T> is often all you need. However for the guys from coding blocks, he wanted something he could add items too. IEnumerable<T> doesn’t provide these methods, but ICollection<T> does.

int Count { get; } //Gets the number of elements contained in the ICollection<T>.bool IsReadOnly { get; } //Gets a value indicating whether the ICollection<T> is read-only.void Add(T) //Adds an item to the ICollection<T>.void Clear() //Removes all items from the ICollection<T>.bool Contains(T) //Determines whether the ICollection<T> contains a specific value.void CopyTo(T[], int) //Copies the elements of the ICollection<T> to an Array, starting at a particular Array index.void Remove(T) //Removes the first occurrence of a specific object from the ICollection<T>.

Now this is where things become useful if you want a finite collection you can add elements to. The property Count requires we have a finite size to our collection. We have our Add(T) method to add items to our collection, and a few more methods alongside.

This is easy to implement, and would probably have served the needs of the the guys from the podcast, without having to implement the whole of IList<T>. I use ICollection<T> all the time. It is usually the simplest interface that serves my needs when dealing with a collection, and rather than requiring the whole of the IList<T> interface, we make our methods more versatile by only requiring the simpler ICollection<T>.

IList<T>

Building on top of ICollection<T>, IList<T> adds just a little more functionality, and that is the idea of an order to our collection.

T Item[int] //Gets or sets the element at the specified index.int IndexOf(T) //Determines the index of a specific item in the IList<T>.int Insert(int, T) //Inserts an item to the IList<T> at the specified index.void RemoveAt(int) //Removes the IList<T> item at the specified index.

So if we need an order to our collection, this is when we should move on from ICollection<T> and use IList<T> instead. But most of the time, we aren’t using indexes, or requiring a guaranteed order, so this is usually overkill.

IReadOnlyCollection<T> and IReadOnlyList<T>

Just a quick note on the two above. IReadOnlyCollection<T> is one of my favourite interfaces when combined with Repositories. You shouldn’t really be adding items directly to the collection you return from your repository (it may lead to unexpected side effects), so returning a read only version of your collection ensures this is not possible.

Final Thoughts

So if you take something out of this article, I hope it is a desire to use the simplest interface that implements what you need. IEnumerable<T> is simple, and provides you a lot of functionality for querying and iterating over collections, but comes with a lot of potential risks (the dangers of IQueryable<T> for example, but that’s another article). ICollection<T> is my favourite of the group, and unless I need a guaranteed order, it fulfils my needs most of the time. Finally, if you need your collection to be in a defined order, IList<T> should be your interface of choice. However if you are only going to do one thing, make that one thing using one of the interfaces above rather than the concrete definition List<T> to make your methods more versatile!

--

--

Paul Burgess

Developer and Consultant, and creator of various open source libraries http://github.com/codepb