The most common interfaces are
IEnumerable<T>
- When you just need some sequence of items to iterate over. May even be of infinite length.
IReadOnlyCollection<T>
- When you also need to know how many items there are
IReadOnlyList<T>
- When you also need to access an item by index
Note that all of these allow the collection to be cast to the actual type, and modified. But this is obviously bad practice, and anyone doing that have to suffer the consequences. These interfaces are implemented by the most common .net collections, like List<T>
, T[]
etc.
Then there is ReadOnlySpan<T>
and ReadOnlyMemory<T>
, but these are more intended for low level code. I would view these as a kind of type safe pointer rather than an interface for a collection.
Old code may use ICollection<T>
or IList<T>
, I dislike these interfaces, since they are not read only, and I consider them rather bloated. There is also a ReadOnlyCollection<T>
, but this implements ICollection<T>
and IList<T>
, and therefore violates Liskov substitution principle, so I would not use it for new code.