IEnumerable vs IReadOnlyList

I apologize to everyone who’s waiting for my response to their code review requests. I was busy dealing with some personal stuff the last couple of weeks. Moving forward, I’m going to maintain a one post a week schedule where "regular" articles would take turns with code reviews. This article is a regular one, so the one next week would be a code review.

Today, I’d like to talk about IEnumerable and IReadOnlyList collection interfaces. We’ll discuss which of them is preferable and in what circumstance.

IEnumerable vs IReadOnlyList

I wrote about similar topics a while back (Return the most specific type, accept the most generic type and IEnumerable interface in .NET and LSP) but hey, that was 2 years ago, and no one read my blog back then. Also, the subject of IEnumerable versus IReadOnlyList was not the primary focus of those posts, and so I decided to reiterate on that. But I encourage you to go ahead and read those articles regardless.

When talking about collections in a method’s signature, the two most popular types programmers use to represent those collections are IEnumerable and IReadOnlyList. Why is that? Why not, say, IEnumerable and ICollection? Or ICollection and IReadOnlyList? And why not just use a concrete collection class, such as List or Dictionary?

The answer to the first question is that people like working with immutable data. Immutability is a big deal in the modern software development world, even if you don’t practice functional programming much. It’s much easier to trace the program flow when it leaves distinct marks during its execution. For example, it’s pretty straightforward to trace what the following code does:

IReadOnlyList<string> names = ExtractNames(data);
IReadOnlyList<string> removedBrackets = RemoveBrackets(names);
IReadOnlyList<string> garbageFiltered = FilterGarbage(removedBrackets);
IReadOnlyList<string> filtered = FilterIncorrectNames(garbageFiltered);

That’s because you have the results of each of the intermediate steps right before you. They are easy to validate and verify.

Now, compare it to something like this:

List<string> names = ExtractPredefinedNames(data);
RemoveBrackets(names);
FilterGarbage(names);
FilterIncorrectNames(names);

Not so much for traceability, huh? A bug could sneak in in any of the above steps and it’s now harder to get to know where exactly it took place because all the evidence has been eradicated by data mutation.

Of course, one could adhere to functional style and preserve immutability even when working with plain List. But in many cases, it’s easier to maintain the order when the programming language itself helps you with that by disallowing changing collections in the first place. And that’s where IEnumerable and IReadOnlyList are helpful. They are the only interfaces in .NET which allow you state your intent upfront and signalize the client of your code that a collection cannot be changed. That is true even if that client is yourself.

Before moving forward, I need to do a couple of remarks here. First, along with IReadOnlyList, there is also IReadOnlyCollection, so technically, there are three such interfaces, not two. There’s no practical reason for using this interface, however, because all collections in .NET implementing IReadOnlyCollection also implement IReadOnlyList. And as the latter is more functional in terms of available API, it makes sense to prefer that interface over IReadOnlyCollection.

Second, the sole fact that some method returns IReadOnlyList or IEnumerable doesn’t guarantee that the underlying collection is immutable. If the client knows what collection type is actually being returned, he can implement a hack like this:

IReadOnlyList<string> names = ExtractPredefinedNames(data);
var mutableNames = (List<string>)names;

and still mutate that collection. This, however, is a gross violation of a basic OOP principle. Which is: you should not assume anything more about an object than what its type is telling you. Got a read-only collection interface? Deal with it appropriately, or else have compatibility issues when the method’s author decides to change the type of the underlying collection.

Alright, so this is the first reason why programmers like to use IEnumerable and IReadOnlyList. It helps "seal" collections, mark them as unchangeable. The second reason is the Open-Closed Principle. Programming to an interface allows you to easily swap the underlying collections as long as they implement the same interface. All that without changing any client code.

So, which one should you prefer? There’s a good practice when it comes to designing your APIs (and I wrote about it here): be conservative in what you send, be liberal in what you accept, also known as Postel’s law.

For our purposes, the "be liberal" part means: accept the most generic type you can. Whereas "be conservative": return the most specific type you can. So, to paraphrase: prefer IEnumerable when accepting a collection; prefer IReadOnlyList when returning one.

Note that IEnumerable is not the most generic collection type possible. The most generic type of all types (including collection types) in .NET is Object. So why not just accept an object instead of IEnumerable? This is for sure would be more liberal than IEnumerable, right?

Actually, no. We still need to adhere to this basic principle saying that our code should not assume anything about an object other than what its type says. In other words, no downcast. So, if an input parameter is of type object, it is incorrect to cast it down to IEnumerable. Better state the required type upfront, that’s why we have strong type systems.

That’s by the way also true for IEnumerable itself. This interface provides only the basic functionality for working with collections (really, just iterating through them), and so if your method requires more than that, also state that requirement up front. For example, if the only thing you need out of a collection is the capability to enumerate its elements, it’s fine to use IEnumerable:

public void SomeMethod(IEnumerable<string> collection)
{
    foreach (string element in collection)
    {
        /* work with element */
    }
}

But if you need to access them by the index or know how many of them are in the collection, use IReadOnlyList:

public void SomeMethod(IReadOnlyList<string> collection)
{
    int count = collection.Count;
            
    /* use count */
 
    string element = collection[10];
}

So, be as liberal with the input collection type as you can, but not more. If you can’t make it work with IEnumerable, use IReadOnlyList.

Similarly, if you can return the more specific collection type from a method, do that. Use IReadOnlyList when possible. Fall back to IEnumerable when not.

Note that for private methods, it’s fine to use concrete collection types, such as List or Dictionary. You need to adhere to OCP only when the method is part of a public API.

Following this simple principle helps the clients of your code be more productive. When passing collections to your methods, they are free to use any concrete collection type because all of them implement IEnumerable. And when working with the output collection, they have the fully functional API on top of it; they are not restricted to using only the bare minimum IEnumerable provides.

Summary

IEnumerable has been with us from the beginning of time. For many years, it was a de facto standard way to represent a read-only collection. Since .NET 4.5, however, there is another way to do that: IReadOnlyList. Both collection interfaces are useful. Prefer IEnumerable when accepting a collection. Prefer IReadOnlyList when returning one.

Subscribe


I don't post everything on my blog. Don't miss smaller tips and updates. Sign up to my mailing list below.

Comments


comments powered by Disqus