Some people say "always use IList
instead of List
".
They want you to change your method signatures from void Foo(List input)
to void Foo(IList input)
.
These people are wrong.
It's more nuanced than that. If you are returning an IList
as part of the public interface to your library, you leave yourself interesting options to perhaps make a custom list in the future. You may not ever need that option, but it's an argument. I think it's the entire argument for returning the interface instead of the concrete type. It's worth mentioning, but in this case it has a serious flaw.
As a minor counterargument, you may find every single caller needs a List
anyway, and the calling code is littered with .ToList()
But far more importantly, if you are accepting an IList as a parameter you'd better be careful, because IList
and List
do not behave the same way. Despite the similarity in name, and despite sharing an interface they do not expose the same contract.
Suppose you have this method:
public Foo(List a)
{
a.Add(someNumber);
}
A helpful colleague "refactors" the method to accept IList
.
Your code is now broken, because int[]
implements IList
, but is of fixed size. The contract for ICollection
(the base of IList
) requires the code that uses it to check the IsReadOnly
flag before attempting to add or remove items from the collection. The contract for List
does not.
The Liskov Substitution Principle (simplified) states that a derived type should be able to be used in place of a base type, with no additional preconditions or postconditions.
This feels like it breaks the Liskov substitution principle.
int[] array = new[] {1, 2, 3};
IList ilist = array;
ilist.Add(4); // throws System.NotSupportedException
ilist.Insert(0, 0); // throws System.NotSupportedException
ilist.Remove(3); // throws System.NotSupportedException
ilist.RemoveAt(0); // throws System.NotSupportedException
But it doesn't. The answer to this is that the example used IList/ICollection wrong. If you use an ICollection you need to check the IsReadOnly flag.
if (!ilist.IsReadOnly)
{
ilist.Add(4);
ilist.Insert(0, 0);
ilist.Remove(3);
ilist.RemoveAt(0);
}
else
{
// what were you planning to do if you were given a read only list anyway?
}
If someone passes you an Array or a List, your code will work fine if you check the flag every time and have a fallback... But really; who does that? Don't you know in advance if your method needs a list that can take additional members; don't you specify that in the method signature? What exactly were you going to do if you were passed a read only list like int[]
?
You can substitute a List
into code that uses IList
/ICollection
correctly. You cannot guarantee that you can substitute an IList
/ICollection
into code that uses List
.
There's an appeal to the Single Responsibility Principle / Interface Segregation Principle in a lot of the arguments to use abstractions instead of concrete types - depend on the narrowest possible interface. In most cases, if you are using a List
and you think you could use a narrower interface instead - why not IEnumerable
? This is often a better fit if you don't need to add items. If you need to add to the collection, use the concrete type, List
.
For me IList
(and ICollection
) is the worst part of the .NET framework. IsReadOnly
violates the principle of least surprise. A class, such as Array
, which never allows adding, inserting or removing items should not implement an interface with Add, Insert and Remove methods. (see also https://softwareengineering.stackexchange.com/questions/306105/implementing-an-interface-when-you-dont-need-one-of-the-properties)
Is IList
a good fit for your organisation? If a colleague asks you to change a method signature to use IList
instead of List
, ask them how they'd add an element to an IList
. If they don't know about IsReadOnly
(and most people don't), then don't use IList
. Ever.
Note that the IsReadOnly flag comes from ICollection, and indicates whether items can be added or removed from the collection; but just to really confuse things, it does not indicate whether they can be replaced, which in the case of Arrays (which return IsReadOnlys == true) can be.
For more on IsReadOnly, see msdn definition of ICollection.IsReadOnly