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.
IEnumerable vs IReadonlyCollection vs ReadonlyCollection for exposing a list member
IEnumerable vs ICollection vs IList
Just a quick note on the two above. IReadOnlyCollection 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.
Performance between Iterating through IEnumerable and List
When you call ToList() on your IEnumerable, you are running a full iteration of your collection and loading all of the elements into memory with that iteration. This is worth doing if you expect to be iterating through the same collection multiple times. If you expect to iterate through the collection only once, then ToList() is a false economy: all it does is to create an in-memory collection that will later have to be garbage collected.
LINQ equivalent of foreach for IEnumerable
There is no ForEach extension for
IEnumerable
; only forList<T>
. So you could doitems.ToList().ForEach(i => i.DoStuff());
Alternatively, write your own ForEach extension method:
public static void ForEach<T>(this IEnumerable<T> enumeration, Action<T> action) { foreach(T item in enumeration) { action(item); } }
Be careful with
ToList()
, becauseToList()
creates a copy of the sequence, which could cause performance and memory issues. – decasteljau Aug 14 '09 at 12:37
IReadOnlyCollection instead of IEnumerable | Code {Sections}
But for the client developer
IEnumerable<T>
may signal something totally different as well: that the service doesn’t actually return the data they want but only a promise to be able to get the data eventually when trying to browse the “collection”. This would automatically raise awareness that it could turn into a performance issue as looping the collection multiple times would actually invoke multiple yield return calls internally within your service implementation. So the client developers will usually do a.ToArray()
themselves to cache results and ensure best performance of their apps.In the latest .NET Framework versions, however, you could use a better, intermediary, interface instead,
IReadOnlyCollection<T>
:IReadOnlyCollection<T> Get();That would clearly signal to the client developer that:
- The data in the collection is already prepared by your service, and no cache is necessary to browse the collection multiple times;
- Collection should be treated as immutable, i.e. the client code should only read it, and not update it; you can also use
ReadOnlyCollection<T>
class internally in the service implementation to host the items if you want to avoid casting of the returned object to a mutable collection on the client side;- There would be no issue if the data would be accessed later, such as after a connection is closed and the original promise of
IEnumerable<T>
would otherwise be compromised. 🙂