IEnumerable<T> Vs IReadOnlyList<T>

Common dilemma developers face when creating methods that return collections is to choose the right return type from the host of collecitons available. It is the difficult to make a choice between IEnumerable<T> vs IReadOnlyList<T>. In this post, we will attempt to address the question and understand the impact of choosing either.

IEnumerable<T> and IReadOnlyList<T>

Let us begin by understanding why developers usually favour IEnumerable<T> or IReadOnlyList<T> as their most preferred choice when returning collection from method, especially when they would like their collection to be immutable. Notice that there are two more choices, IReadOnlyCollection<T> and IReadOnlyDictionary<TKey,TValue>, but for sake of simplicity, we will restrict the discussion to IReadOnlyList<T>.

Let us illustrate the problem by returning an IList<T> from a method.

public class Foo {
    private IList < int > _internalCollection;
    public Foo() {
        _internalCollection = new List < int > {
            1,
            2,
            3,
            4
        };
    }
    public IList < int > GetList() => _internalCollection;
    public void Print() => Console.WriteLine($ "List => [{string.Join(", ",_internalCollection)}]");
}
void Main() {
    var foo = new Foo();
    foo.Print();
    var list = foo.GetList();
    list.Add(10);
    foo.Print();
}

We have defined a Foo class, which exposes two methods - GetList() which returns an internal collection, and a Print() method for printing the internal list to console. Let us execute our code and check the output. The consuming code creates an instance of the class and prints the collection using Print() method. After which, it retrieves the collection using the Get() method and then it adds a new item to the received List.

List => [1,2,3,4]  // Before adding element in client code
List => [1,2,3,4,10] // After adding element in client code

Note that we do not intensionally expose our internal collection outside Foo. However, since you are returning a mutable collection List<T> using the Get() method, it unintentionally exposes our internal collection for corruption from consuming code. As demonstrated, the consuming code is now free to modify the internal collection, which might not be exactly what the developer would have thought of.

IEnumerable<T> vs IReadOnlyList<T>

IEnumerable<T> and IReadOnlyList<T> both returns an immutable collection (in common use case - more on this shortly), which cannot be altered by the consuming code. So how should developer choose between them?

IEnumerabl<T> of course, is different. It promises the consuming code to fetch the items as it is accessed (lazy evaluation). This would not be an ideal choice if the collections have to be iterated multiple times. In such scenarios, the consuming code would have to cache the results internally by materializing the results using ToList() or ToArray(). Furthermore, if the consuming code is expected to access the results using index, it might be a better choice to return the result as IReadOnlyList<T>.

However, on the other hand, if you expect the consuming code to stop evaluating the collection prematurely (might not require entire collection), then IEnumerable<T> could be a great choice. But one needs to be wary of certain limitations of IEnumerable<T>.

Let us rewrite the above Get() method with IEnumerable<int>.

private IList<int> _internalCollection;
public IEnumerable<int> GetList() => _internalCollection;

Of course at this point the compiler would throw error in the consuming code complaining 'IEnumerable<int>' does not contain a definition for 'Add'. Furthermore, since IEnumerable<T> cannot be accessed via index, items cannot be replaced either. But does that make it truly immutable? Well, think again.

if the consuming code is aware of the underlying collection(in this case, List<int>), it can quite literally hack into the collection. Let us rewrite the consuming code now.

var foo = new Foo();
foo.Print();
var list = (List<int>)foo.GetList();
list.Add(10);
foo.Print();

Notice how we have cast the IEnumerable<int> returned by the Get() method to the underlying collection. This allows us to still modify the internal collection, which is again undesirable. Again, this would not have been possible in case we were yielding results, which is another story.

// output
List => [1,2,3,4]
List => [1,2,3,4,10]

This is where IReadOnlyList() (or IReadOnlyCollection<T>) truly comes into play. It tells the consuming code that the collection is truly immutable and prohibits modification.

public IReadOnlyList<int> GetList() => _internalCollection.AsReadOnly();

Conclusion

As a developer, one should understand the nature of your code and pick your choices. If you need a truly immutable collection, without the risk of consuming code knowing and using the underlying collection type to modify your collection, the readonly coolections like the IReadOnlyList<T> and IReadOnlyCollection<T> could be your choice. However, if you would like the collection to be truly not cached and evaluated lazy, then you need to pick IEnumerble<T> and yield the results as the consuming code requires data in collection.


Similar Articles