Returning 'IList' vs 'ICollection' vs 'Collection'

69,734

Solution 1

Generally you should return a type that is as general as possible, i.e. one that knows just enough of the returned data that the consumer needs to use. That way you have greater freedom to change the implementation of the API, without breaking the code that is using it.

Consider also the IEnumerable<T> interface as return type. If the result is only going to be iterated, the consumer doesn't need more than that.

Solution 2

ICollection<T> is an interface that exposes collection semantics such as Add(), Remove(), and Count.

Collection<T> is a concrete implementation of the ICollection<T> interface.

IList<T> is essentially an ICollection<T> with random order-based access.

In this case you should decide whether or not your results require list semantics such as order based indexing (then use IList<T>) or whether you just need to return an unordered "bag" of results (then use ICollection<T>).

Solution 3

The main difference between the IList<T> and ICollection<T> interfaces is that IList<T> allows you to access elements via an index. IList<T> describes array-like types. Elements in an ICollection<T> can only be accessed through enumeration. Both allow the insertion and deletion of elements.

If you only need to enumerate a collection, then IEnumerable<T> is to be preferred. It has two advantages over the others:

  1. It disallows changes to the collection (but not to the elements, if they are of reference type).

  2. It allows the largest possible variety of sources, including enumerations that are generated algorithmically and are not collections at all.

  3. Allows lazy evaluation and can be queried with LINQ.

Collection<T> is a base class that is mainly useful to implementers of collections. If you expose it in interfaces (APIs), many useful collections not deriving from it will be excluded.


One disadvantage of IList<T> is that arrays implement it but do not allow you to add or remove items (i.e. you cannot change the array length). An exception will be thrown if you call IList<T>.Add(item) on an array. The situation is somewhat defused as IList<T> has a Boolean property IsReadOnly that you can check before attempting to do so. But in my eyes, this is still a design flaw in the library. Therefore, I use List<T> directly, when the possibility to add or remove items is required.


Which one should I choose? Let's consider just List<T> and IEnumerable<T> as examples for specialized / generalized types:

  • Method input parameter
    • IEnumerable<T> greatest flexibility for the caller. Restrictive for the implementer, read-only.
    • List<T> Restrictive for the caller. Gives flexibility to the implementer, can manipulate the collection.
  • Method ouput parameter or return value
    • IEnumerable<T> Restrictive for the caller, read-only. Greatest flexibility for the implementer. Allows to return about any collection or to implement an iterator (yield return).
    • List<T> Greatest flexibility for the caller, can manipulate the returned collection. Restrictive for the implementer.

Well, at this point you may be disappointed because I don't give you a simple answer. A statement like "always use this for input and that for output" would not be constructive. The reality is that it depends on use case. A method like void AddMissingEntries(TColl collection) will have to provide a collection type having an Add method or may even require a HashSet<T> for efficiency. A method void PrintItems(TColl collection) can happily live with an IEnumerable<T>.

Solution 4

IList<T> is the base interface for all generic lists. Since it is an ordered collection, the implementation can decide on the ordering, ranging from sorted order to insertion order. Moreover Ilist has Item property that allows methods to read and edit entries in the list based on their index. This makes it possible to insert, remove a value into/from the list at a position index.

Also since IList<T> : ICollection<T>, all the methods from ICollection<T> are also available here for implementation.

ICollection<T> is the base interface for all generic collections. It defines size, enumerators and synchronization methods. You can add or remove an item into a collection but you cannot choose at which position it happens due to the absence of index property.

Collection<T> provides an implementation for IList<T>, IList and IReadOnlyList<T>.

If you use a narrower interface type such as ICollection<T> instead of IList<T>, you protect your code against breaking changes. If you use a wider interface type such as IList<T>, you are more in danger of breaking code changes.

Quoting from a source,

ICollection, ICollection<T> : You want to modify the collection or you care about its size. IList, IList<T>: You want to modify the collection and you care about the ordering and / or positioning of the elements in the collection.

Solution 5

Returning an interface type is more general, so (lacking further information on your specific use case) I'd lean towards that. If you want to expose indexing support, choose IList<T>, otherwise ICollection<T> will suffice. Finally, if you want to indicate that the returned types are read only, choose IEnumerable<T>.

And, in case you haven't read it before, Brad Abrams and Krzysztof Cwalina wrote a great book titled "Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries" (you can download a digest from here).

Share:
69,734
Rocky Singh
Author by

Rocky Singh

Updated on August 24, 2021

Comments

  • Rocky Singh
    Rocky Singh over 2 years

    I am confused about which collection type that I should return from my public API methods and properties.

    The collections that I have in mind are IList, ICollection and Collection.

    Is returning one of these types always preferred over the others, or does it depend on the specific situation?

  • Adam Mihalcin
    Adam Mihalcin about 12 years
    +1 for a good answer. However, I'd recommend adding a few notes about the general principle of abstraction, and why it favors returning interface types rather than concrete class types.
  • CodesInChaos
    CodesInChaos about 12 years
    Collection<T> implements IList<T> and not only ICollection<T>.
  • svick
    svick about 12 years
    All collections in .Net are ordered, because IEnumerable<T> is ordered. What distinguishes IList<T> from ICollection<T> is that it offers members to work with indexes. For example list[5] works, but collection[5] won't compile.
  • CodesInChaos
    CodesInChaos about 12 years
    I also disagree that ordered collections should implement IList<T>. There are plenty of ordered collections that don't. IList<T> is about fast indexed access.
  • retrodrone
    retrodrone over 11 years
    But also consider ICollection<T> which extends IEnumerable<T> to provide additional utilities including a Count property. This can be helpful because you can determine the length of the sequence without traversing the sequence multiple times.
  • Guffa
    Guffa over 11 years
    @retrodrone: Actually the implementation of the Count method checks the actual type of the collection, so it will use the Length or Count properties for some known types like arrays and ICollection even when you supply it as an IEnumerable.
  • atconway
    atconway over 11 years
    "If you don't have much experience with interfaces, I recommend stick to classes" This is not good advice. That's like saying if there is something you don't know, just stay away from it. Interfaces are extremely powerful and back up the concept of "Program to abstractions vs. concretions" They are also quite powerful for doing unit testing because of the ability to interchange types via mocks. There are a bunch of other great reasons to use Interfaces beyond these comments, so please by all means explore them.
  • umlcat
    umlcat over 11 years
    @atconway I regulary use interfaces, but, as many concepts, its a powerful tool, it can be easy to mix-use. And, sometimes, developer may not need it. My suggestion wasn't "don't ever interfaces", my suggestion was "in this case, you may skip the interfaces. Learn about it, and you may get the best of them, later". Cheers.
  • supercat
    supercat about 11 years
    @Guffa: It may be worthwhile to note that if a class Thing<T> implements IList<T> but not the non-generic ICollection, then calling IEnumerable<Cat>.Count() on a Thing<Cat> would be fast, but calling IEnumerable<Animal>.Count() would be slow (since the extension method would look for, and not find, an implementation of ICollection<Cat>). If the class implements the non-generic ICollection, however, IEnumerable<Animal>.Count would find and use that.
  • supercat
    supercat about 11 years
    It's worth noting that while IList<T> is a generic equivalent to IList, ICollection<T> and ICollection are very different. A generic Foo<T> which implements IList<T> should often not implement non-generic IList, but should still implement the non-generic ICollection. Otherwise, even though Foo<Cat> would implement IEnumerable<Animal> and ICollection<Cat>, and the latter would expose a fast Count property, the IEnumerable<Animal>.Count() extension method wouldn't be able to use the ICollection<Cat>.Count() implementation.
  • yoyo
    yoyo about 11 years
    @CodesInChaos points out that Collection<T> implements IList<T> -- this seems odd to me, anyone know why this was done this way?
  • CodesInChaos
    CodesInChaos about 11 years
    @yoyo The .net collection classes and interfaces are simply badly designed and badly named. I wouldn't think too much about why they named them like that.
  • mac10688
    mac10688 about 10 years
    I recommend always programming to an interface. It helps keep code loosely coupled.
  • umlcat
    umlcat about 10 years
    @mac10688 My previous answer (atconway) also applies to your comment.
  • gbackmania
    gbackmania almost 10 years
    @svick sorry, late to the party, but Collection[i] does compile. Am I missing something?
  • svick
    svick almost 10 years
    @GBackMania No it doesn't: ICollection<int> collection = new int[1]; Console.WriteLine(collection[0]);. I assume you tried it with Collection<T>, not ICollection<T>, but that's not what I was talking about.
  • gbackmania
    gbackmania almost 10 years
    @svick Yes you are right. I thought you meant Collection<T>. Another question - What do you mean by IEnumerable<T> is ordered? Isn't that an implementation detail? By the way, order might hold true for an in-memory list but not a list from db.
  • Kyle
    Kyle over 9 years
  • Olivier Jacot-Descombes
    Olivier Jacot-Descombes over 9 years
    @svick: Are all collections ordered because IEnumerable<T> is ordered? Take HashSet<T> - it implements IEnumerable<T> but is clearly not ordered. I.e. you have no influence on the order of the items and this order could change at any time, if the internal hash table is re-organized.
  • DavidRR
    DavidRR over 8 years
    Code Analysis (and FxCop) advises that when returning a concrete generic collection, one of the following should be returned: Collection, ReadOnlyCollection or KeyedCollection. And that List should not be returned.
  • zumalifeguard
    zumalifeguard about 8 years
    I disagree. It's best to return the richest type that you have so that the client can use it directly if that's what they want. If you have a List<>, why return an IEnuerable<> only to force the caller to call ToList() unnecessarily ?
  • Piotr Kula
    Piotr Kula about 7 years
    IEnumerable<T> is read only? Sure but when retuning it in a context of repository, even after executing ToList is not thread safe. Problem is when the original data source gets GC then IEnumarble.ToList() does not work in a multi threaded context. It works on a linear one becasue GC gets called after you do the work but using async now a days in 4.5+ IEnumarbale is becoming a ball ache.
  • Olivier Jacot-Descombes
    Olivier Jacot-Descombes almost 7 years
    @DavidRR: Often it is usefull to pass a list to a method that has to add or remove items. If you type the parameter as IList<T>, there is no way to ensure that the method will not be called with an array as parameter.
  • Timeless
    Timeless over 6 years
    @svick What do you mean by ordered ? I'm a bit confused by this word, do you mean it is chained like a linked list?
  • svick
    svick over 6 years
    @Timeless I'm not talking about implementation, just about the fact that if you iterate a collection using foreach, you get the elements in some order, which is defined by the collection.
  • ToolmakerSteve
    ToolmakerSteve over 4 years
    @zumalifeguard - IMHO, IList<> is a good compromise, when you desire to return a rich type, without locking to a specific type.
  • ToolmakerSteve
    ToolmakerSteve over 4 years
    @svick - your definition of the term order is not useful. It is a tautology that if you ask a bag of items to be given to you one at a time, that the resulting sequence is in some order. It would be meaningless to define ordered as the ability to iterate over a grouping of items - that is always possible. Therefore, that is not how "ordered" is defined. An "ordered" collection is one which provides some guarantee of sequence order that is seen, if enumerate multiple times - with other actions in-between (add/delete). Unfortunately the standard classes are vague re their contracts.
  • ToolmakerSteve
    ToolmakerSteve over 4 years
    ... for example, we "know" (by convention; AFAIK it is not definitively specified) that if you add items to the end of a List, you can delete items in the middle of the list, without changing the order of the remaining items. You can do Add(22); Add(11); Add(44); Remove(11); Add(33); and know the result is [22, 44, 33] - the original order of Adds. No such guarantee is made for HashSet, or for the Keys of a Dictionary; as you add/delete items, nothing is guaranteed re enumeration order. List is referred to as "ordered"; HashSet or Dictionary.KeyCollection are not ordered.
  • ToolmakerSteve
    ToolmakerSteve over 4 years
    ... Similarly, you can do Insert in a list, and know that this doesn't "scramble" the list. In my example above, if then do Insert(1, 55), the result is [22, 55, 44, 33] - you can be confident that the pre-existing elements don't move around in some unpredictable way; the items are said to maintain their order. Another example of "maintaining order": Before each order-preserving operation (add/insert/remove), item 22 was somewhere before 33 - after that operation, 22 is still somewhere before 33. The only change is that there may be more or fewer items between the two.