The extension method ToList()
returns a List
. Following the same pattern, ToDictionary()
returns a Dictionary<
If a function returns a newly-constructed immutable object, the caller should generally not care about the precise type returned provided it is capable of holding the actual data that it contains. For example, a function that is supposed to return an IImmutableMatrix
might normally return an ImmutableArrayMatrix
backed by a privately-held array, but if all the cells hold zeroes it might instead return an ZeroMatrix
, backed only by Width
and Height
fields (with a getter that simply returns zero all the time). The caller wouldn't care whether it was given an ImmutableArrayMatrix
matrix or a ZeroMatrix
; both types would would allow all of their cells to be read, and guarantee their values would never change, and that's what the caller would care about.
On the other hand, functions that return newly-constructed objects that allow open-ended mutation should generally return the precise type the caller is going to expect. Unless there will be a means by which the caller can request different return types (e.g. by calling ToyotaFactory.Build("Corolla")
versus ToyotaFactory.Build("Prius")
) there's no reason for the declared return type to be anything else. While factories that return immutable data-holding objects can select a type based on the data to be contained, factories that return freely-mutable types will have no way of knowing what data may be put into them. If different callers will have different needs (e.g. returning to the extant example, some callers' needs would be met with an array, while others' would not) they should be given a choice of factory methods.
BTW, something like IEnumerator<T>.GetEnumerator()
is a bit of a special case. The returned object will almost always be mutable, but only in a very highly-constrained fashion; indeed, it is expected that the returned object regardless of its type will have exactly one piece of mutable state: its position in the enumeration sequence. Although an IEnumerator<T>
is expected to be mutable, the portions of its state which would vary in derived-class implementations are not.
There are a number of advantages to just having a List
over an IList
. To begin with, List
has methods that IList
does not. You also know what the implementation is which allows you to reason about how it will behave. You know it can efficiently add to the end, but not the start, you know that it's indexer is very fast, etc.
You don't need to worry about your structure being changed to a LinkedList
and wrecking the performance of your application. When it comes to data structures like this it really is important in quite a lot of contexts to know how your data structure is implemented, not just the contract that it follows. It's behavior that shouldn't ever change.
You also can't pass an IList
to a method accepting a List
, which is something that you see quite a lot of. ToList
is frequently used because the person really needs an instance of List
, to match a signature they can't control, and IList
doesn't help with that.
Then we ask ourselves what advantages there are to returning IList
. Well, we could possibly return some other implementation of a list, but as mentioned before this is likely to have very detrimental consequences, almost certainly much more than could possibly be gained from using any other type. It might give you warm fuzzies to be using an interface instead of an implementation, but even that is something I don't feel is a good mentality (in general or) in this context. As a rule returning an interface is generally not preferable to returning a concrete implementation. "Be liberal in what you accept and specific in what you provide." The parameters to your methods should, where possible, be interfaces defining the least amount of functionality you need to that your caller can pass in any implementation that does what you need of it, and you should provide as concrete of an implementation as the caller is "allowed" to see so that they can do as much with the result as that object is capable of. Passing an interface is restricting that value, which is only occasionally something that you want to do.
So now we move onto, "Why return ILookup
and not Lookup
?" Well, first off Lookup
isn't a public class. There is no Lookup
in System.Collections.*
. The Lookup
class that is exposed through LINQ exposes no constructors publicly. You're not able to use the class except through ToLookup
. It also exposes no functionality that isn't already exposed through ILookup
. In this particular case they designed the interface specifically around this exact method (ToLookup
) and the Lookup
class is a class specifically designed to implement that interface. Because of all of this virtually all of the points discussed about List
just don't apply here. Would it have been a problem to return Lookup
instead, no, not really. In this case it really just doesn't matter much at all either way.
Because List<T>
actually implements a range of interfaces, not just IList:
public class List<T> : IList<T>, ICollection<T>, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>, IEnumerable<T>, IEnumerable{
}
Each of those interfaces define a range of features which the List must conform. Picking one particular one, would render bulk of the implementation unusable.
If you do want to return IList, nothing stops you from having your own simple wrapper:
public static IList<TSource> ToIList<TSource>(this IEnumerable<TSource> source)
{
if (source == null) throw new ArgumentNullException(source);
return source.ToList();
}
In general when you call ToList() on a method you're looking for a concrete type otherwise the item could stay as type IEnumerable. You don't need to convert to a List unless you're doing something that requires a concrete list.