68

The extension method ToList() returns a List<TSource>. Following the same pattern, ToDictionary() returns a Dictionary<TKey, TSource>.

I am curious why those methods do not type their return values as IList<TSource> and IDictionary<TKey, TSource> respectively. This seems even odder because ToLookup<TSource, TKey> types its return value as an interface instead of an actual implementation.

Looking at the source of those extension methods using dotPeek or other decompiler, we see the following implementation (showing ToList() because it is shorter):

public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source) { 
   if (source == null) throw Error.ArgumentNull("source");
   return new List<TSource>(source); 
}

So why does this method type its return value as a specific implementation of the interface and not the interface itself? The only change would be the return type.

I am curious because the IEnumerable<> extensions are very consistent in their signatures, except for those two cases. I always thought it to be a bit strange.

Additionally, to make things even more confusing, the documentation for ToLookup() states:

Creates a Lookup from an IEnumerable according to a specified key selector function.

but the return type is ILookup<TKey, TElement>.

In Edulinq, Jon Skeet mentions that the return type is List<T> instead of IList<T>, but does not touch the subject further.
Extensive searching has yielded no answer, so here I ask you:

Is there any design decision behind not typing the return values as interfaces, or is it just happenstance?

Daniel Imms
  • 47,944
  • 19
  • 150
  • 166
Waldfee
  • 833
  • 6
  • 10
  • 3
    List implements IList. You can't instantiate an interface, you have to instantiate something. Even IEnumerable is really something else in the background, you're just using an interface to access it. But an interface isn't a whole object. – sircodesalot Feb 07 '13 at 14:15
  • 15
    @sircodesalot Waldfee is clearly aware of this. His entire question is predicated on that fact! – RB. Feb 07 '13 at 14:16
  • 2
    @sircodesalot: Indeed. And if that wouldn't be the case, this question wouldn't make no sense. – Daniel Hilgarth Feb 07 '13 at 14:16
  • 1
    The doc for `ToLookup` also says that it *will* return a `Lookup<,>` as its implementation of `ILookup<,>` – AakashM Feb 07 '13 at 14:17
  • 1
    @AakashM Yes, i am aware that the doc is just specifying the implementation that is returned, but as caller i should not have to care about it, it's return type is an interface after all. – Waldfee Feb 07 '13 at 14:19
  • 1
    @sircodesalot I am aware of that. The method has to instantiate a concrete implementation of course, but it could return the interface nevertheless. – Waldfee Feb 07 '13 at 14:20
  • 3
    I suppose it doesn't matter - since it has to wrap the list in a concrete type and return something to the caller it might as well return the list since that's the particular implementation of `IList` that was used. It makes no odds to you as long as it returns an `IList` implementation. To be fair the method is also called `ToList` not `ToIList` – Charleh Feb 07 '13 at 14:23
  • Oh sure, and in some situations that makes sense. For example LINQ does it because by returning the general IEnumerable rather than a specific class, it can target a much wider range of classes. When you convert to a list though, the intent is to shift to a very specific type of functionality. Generally when you move to a list/array/dictionary, the LINQ aspect of your query is done, and you want a different set of functionality. – sircodesalot Feb 07 '13 at 14:24
  • @Charleh You are right, and it does not make a difference in practice. It's just the fact that it is this way is what i am asking. – Waldfee Feb 07 '13 at 14:25
  • @Charleh: That's why it's called AsEnumerable not AsIEnumerable! :P – sircodesalot Feb 07 '13 at 14:28
  • @AakashM Thanks for editing btw. – Waldfee Feb 07 '13 at 14:32
  • Hahaah - I know I know, it's one of those isn't it! :D – Charleh Feb 07 '13 at 14:33
  • 4
    I'm not sure we can get a definitive answer without comment from the .Net design team but we can assume it is a pragmatic choice. The `List` class is more featured with richer extensions than the `IList` interface. There was a choice to make between utility and purity. – Jodrell Feb 07 '13 at 14:39
  • @Jodrell "There was a choice to make between utility and purity" - reasonable and to the point. – Waldfee Feb 07 '13 at 15:08
  • Why would you want to return *less* type information? It would seem to me that setting your return type to the most specific type you can use should be your default choice unless there's a compelling reason not to do so. – Joren Feb 07 '13 at 16:42

10 Answers10

50

Returning List<T> has the advantage that those methods of List<T> that are not part of IList<T> are easily used. There are a lot of things you can do with a List<T> that you cannot do with a IList<T>.

In contrast, Lookup<TKey, TElement> has only one available method that ILookup<TKey, TElement> does not have (ApplyResultSelector), and you probably would not end up using that anyway.

  • 8
    This could be the answer but, how can we know without a record of the desicions made during the design. (+1) – Jodrell Feb 07 '13 at 14:40
  • 2
    Agreed, the question starts as "why does", not "why would". I trust that no one misinterpreted my answer as the rationale instead of a rationale. :) –  Feb 07 '13 at 15:30
  • 4
    +1 for correct answer. However, Microsoft should *really* take those methods specific to `List` and change them to extension methods that operate on `IList` - that way, **all** `IList` implementations could benefit from `Reverse()`, `Sort()`, etc., not just `List`! I suggested this on MS Connect a few years ago; it got a lot of upvotes, and they said it was a great idea, but kept pushing it out to the next release. Then they [deleted the suggestion](https://connect.microsoft.com/VisualStudio/feedback/details/552410) altogether :( – BlueRaja - Danny Pflughoeft Feb 07 '13 at 23:27
  • @BlueRaja-DannyPflughoeft: A major problem with adding such extension methods is that it's hardly clear what the semantics should be. Should calling one of them remove all the items from a collection and add them in the right order, or should it remove each item and reinsert it in its proper place, or what? I wish those things had been included in `IList` but that the Framework had provided methods that implementers could chain to, along with a better way to query what actions a collection could support and what degree of mutability or immutability it could promise. – supercat Feb 08 '13 at 00:22
  • @supercat I don't an issue here - the implementation of `Reverse()` etc. should be whatever they document it to be. There's no reason not to do it in-place, so I imagine that what they'd do. Also, my suggestion is to add those extension methods to `IList`, *NOT* to the more general `ICollection` - by implementing `IList`, the implementer is already agreeing to a certain degree of mutability. – BlueRaja - Danny Pflughoeft Feb 08 '13 at 00:40
  • Accepting this as answer because it is very short and to the point. As Jodrell commented, only the design team really knows the answer, but this sounds like it would be the reason. – Waldfee Feb 08 '13 at 17:29
12

These kind of decisions may feel arbitrary but I guess that ToList() returns List<T> rather than an interface because List<T> both implements IList<T> but it adds other members not present in a regular IList<T>-typed object.

For example, AddRange().

See what IList<T> should implement (http://msdn.microsoft.com/en-us/library/5y536ey6.aspx):

public interface IList<T> : ICollection<T>, 
    IEnumerable<T>, IEnumerable

And List<T> (http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx):

public class List<T> : IList<T>, ICollection<T>, 
    IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>, IEnumerable<T>, 
    IEnumerable

Maybe your own code doesn't require IReadOnlyList<T>, IReadOnlyCollection<T> or ICollection, but other components on .NET Framework and other products may rely on a more specialized list object and that's why .NET dev team decided to do not return an interface.

Don't feel always return an interface is the best practice. It's if your code or third-party ones require such encapsulation.

Matías Fidemraizer
  • 63,804
  • 18
  • 124
  • 206
  • Very clear explanation, thank you. I seldomly use the functionality `List` implements in addition to `IList`, and therefore did not think of it. – Waldfee Feb 07 '13 at 14:37
  • As for your last paragraph, returning interfaces or concrete types is a situational decision and not one fits all, like everything - my question was specifically about this case. – Waldfee Feb 07 '13 at 14:39
  • 1
    @Waldfee No problem. Think that .NET FW is the foundation for a lot of things. Maybe some types and decisions aren't reflected in the framework itself but by other products' requirements or even user stories. I'm not from the .NET devteam, but this should be the reason. About the last paragraph, of course, I just wanted to point out that returning interfaces isn't always a requirement :) – Matías Fidemraizer Feb 07 '13 at 14:41
  • The question isn't what `IList` "should" implement, but what it actually includes. There are a fair number of things which should be in `IList` but aren't; even though things like `Sort` or `AddRange` could be handled by a static helper methods which take an `IList` and operate on individual items, and many implementations would likely chain to such methods, performance-sensitive implementations could in many cases achieve a better-than-2x (if not 10x) speed boost by implementing such operations directly on their backing stores. – supercat Feb 07 '13 at 15:59
  • @supercat And my answer is that the discussion is that there're exceptions to the rule and if a lot of code depends on using `List` why forcing to **always** downcast only because the rest of the methods return `IList`. This should be the reason and, in fact, this is how I do decisions in my own code. There's not a single pattern for solving everything. And I'm agree with you: maybe some `List`-specific methods should be implemented as extension methods. And `IList` should be able of doing an `AddRange`too. – Matías Fidemraizer Feb 07 '13 at 16:09
  • @MatíasFidemraizer: The use of extension methods to do tasks which should have been in the interface won't help the performance issue unless another interface is defined that includes the methods in question. If there's no way for an `AddRange` extension method to know if a type implements an `AddRange` method with the expected behavior, it will be stuck adding members one by one even if the type could perform an `AddRange` via faster means. – supercat Feb 07 '13 at 16:29
  • @supercat Who knows. I didn't check how `AddRange` is implemented via reflector, but an add range isn't nothing different than an iteration for adding items one by one but using a shortcut. Maybe I'm wrong. And if it's at least a list and it has `Add`, an extension method would work in the same way. I believe that the reason of something like `AddRange` in the concrete list implementation is just because it's from the early .NET times and it should remain there because compatiblity things. – Matías Fidemraizer Feb 07 '13 at 16:35
  • @MatíasFidemraizer: Adding 1000 items one by one to a `List` that presently holds 16 will will require it to generate an array of size 32, then 64, 128, 256, and 512, before finally creating one of size 1024. A properly-written `AddRange` method which was given a `T[1000]` could expand the array once, and then use `Array.Copy` to grab the elements. Big performance win. – supercat Feb 07 '13 at 17:29
  • @supercat Yeah. By the way, this is extending the discussion out of the question and the answer range. ArgumentOutOfRangeException: _The question was very simple, while the argument is too complex_ :D – Matías Fidemraizer Feb 07 '13 at 21:16
8

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.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • Good and thorough answer, although i have to disagree with the first part of the last paragraph: `System.Linq.Lookup<>` is publicly accessible, and just because it does not have a public constructor does not mean it cannot be used as a return type. The second part is a nice explanation to why the return type is the interface though. – Waldfee Feb 07 '13 at 16:16
  • @Waldfee It's not designed to be publicly accessible. It's technically `public`, from a code perspective, but it's not *designed* to be used publicly, so you can *treat* the class as if it were internal. – Servy Feb 07 '13 at 16:33
6

In my opinion returning a List<T> is justified by the fact that the method name says ToList. Otherwise it would have to be named ToIList. It is the very purpose of this method to convert an unspecific IEnumerable<T> to the specific type List<T>.

If you had a method with an unspecific name like GetResults, then a return type like IList<T> or IEnumerable<T> would seem appropriate to me.


If you look at the implementation of the Lookup<TKey, TElement> class with reflector, you'll see a lot of internal members, that are only accessible to LINQ itself. There is no public constructor and Lookup objects are immutable. Therefore there would be no advantage in exposing Lookup directly.

Lookup<TKey, TElement> class seems to be kind of LINQ-internal and is not meant for public use.

Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
  • 2
    But why is `ToLookup()`'s return type an interface then? – Waldfee Feb 07 '13 at 14:41
  • 3
    @Waldfee: Probably because the object it returns is logically immutable, meaning that the factory can freely optimize the return type based upon what it will contain. For example, if the instance to be returned has exactly one item, it would be silly to build a hash table for it. Having `ToLookup` return an interface makes it possible for it return a different concrete type in such a case; even if the present implementation doesn't optimize that case, a future implementation could do so. – supercat Feb 07 '13 at 19:33
4

I believe that the decision to return a List<> instead of an IList<> is that one of the more common use cases for calling ToList is to force immediate evaluation of the entire list. By returning a List<> this is guaranteed. With an IList<> the implementation can still be lazy, which would defeat the "primary" purpose of the call.

Jay Traband
  • 17,053
  • 1
  • 23
  • 44
4

This is one of the common things that programmers have difficulty understanding around the use of interfaces and concrete types.

Returning a concrete List<T> that implements IList<T> only gives the method consumer more information. Here is what the List object implements (via MSDN):

[SerializableAttribute]
public class List<T> : IList<T>, ICollection<T>, IList, ICollection, 
    IReadOnlyList<T>, IReadOnlyCollection<T>, IEnumerable<T>, IEnumerable

Returning as a List<T> gives us the ability to call members on all of these interfaces in addition to List<T> itself. For example we could only use List.BinarySearch(T) on a List<T>, as it exists in List<T> but not in IList<T>.

In general to maximize flexibility of our methods, we should take the most abstract types as parameters (ie. only the things we're going to use) and return the least abstract type possible (to allow a more functional return object).

Daniel Imms
  • 47,944
  • 19
  • 150
  • 166
3

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.

JLRishe
  • 99,490
  • 19
  • 131
  • 169
Robert
  • 4,306
  • 11
  • 45
  • 95
  • 5
    @Default He didn't post to [English.stackexchange.com](http://english.stackexchange.com) ;-) – RB. Feb 07 '13 at 14:21
  • 3
    I don't like `IList` because arrays pretend to implement it, but when you to call `IList` specific methods like `Add` or `Remove` you get an exception :-(. Sure you can test the `IsReadOnly` property, but still, it seems to me that narrowing down (or at least limiting) an interface instead of extending it is a violation of the Liskov Substitution Principle. – Olivier Jacot-Descombes Feb 07 '13 at 14:24
  • It's good API design to consume interfaces but return implementations (as a general rule anyway; returning arrays should be prohibited). – Morten Mertner Feb 07 '13 at 14:25
  • @MortenMertner Really? I would have thought it would [be the other way](http://stackoverflow.com/questions/2679508/concrete-types-or-interfaces-for-return-types). Have you got any references discussing this? – RB. Feb 07 '13 at 14:28
  • 3
    @MortenMertner, if you consider a function's return type as promise it has to keep, it makes sense to make a smaller promise. – Jodrell Feb 07 '13 at 14:52
  • @JustinPihony it didn't bother me "that much". If it did, I would have edited it – default Feb 07 '13 at 15:10
  • @Default Then why even comment. That is a bit of trolling IMO /shrug – Justin Pihony Feb 07 '13 at 15:23
  • @OlivierJacot-Descombes: Even worse than the fact that arrays don't support insertion/deletion is the fact that if an `IList(Control)` happens to be a `Button[]`, storing a `Control` that happens to be a `Button` will work, but storing a `Control` that isn't a `Button` will fail. It's not a violation of the LSP for an interface to have members that are not useful on all implementations if there is a means of querying which members are useful. An array could legitimately implement `IList` and `IList` for read-only access, but in that case no indexed writes should succeed. – supercat Feb 07 '13 at 15:24
  • @RB, it makes sense if you think about it: an interface describes something you want to abstract away. Having your code consume interfaces makes it more flexible and less coupled to specific implementations. Returning an implementation provides the greatest amount of comfort for your callers and is fine as long as you think the return type isn't going to change. The argument that you may not want to support the full type is a bit moot for types built-in to the BCL, but valid otherwise. – Morten Mertner Feb 07 '13 at 15:38
  • @MortenMertner Sorry - I should have been clearer. I absolutely agree that you should consume interfaces - unit-testing would be a damn sight harder if you didn't! But I generally return interfaces, for the reason Jodrell gave. Take the [ExecuteReader] method on `IDBCommand` for example - it returns an IDataReader, to allow the specific type of reader (database, XML, CSV-file, etc.) to be determined at run-time. – RB. Feb 07 '13 at 15:51
  • @supercat: Yes, the variance of arrays is practical but feels a bit like designed for VB and is in my eyes a design flaw of .NET. LSP is not violated in the strict sense, but it feels like it was. It is difficult to use `IList` with the uncertainty of whether the returned object will be updatable or not. – Olivier Jacot-Descombes Feb 07 '13 at 15:59
  • @MortenMertner: See my answer. Code which calls a factory to get a mutable object should generally not be expected to be satisfied with anything other than the precise type it expects; if there's only one type the caller is going to expect, the factory's return value should be that type. – supercat Feb 07 '13 at 16:06
  • @RB The interface IDBCommand specifies the return type, and so the concrete implementations must follow. Of course, it is a good example of when you would return an interface even when the individual methods have a known concrete return type, namely when the method is itself (part of) an interface implementation. – Morten Mertner Feb 07 '13 at 16:08
  • @OlivierJacot-Descombes: The behavior of array variance was inherited from Java. I wish .net had included four array types from the beginning: `ReadableArray`, `ImmutableArray`, `SwappableArray`, and `Array` (IA and SA derived from RA; Array derived from SA). I don't think such a design would have hurt performance, since there would have been no need for virtual members; all compiler-permitted operations would behave identically for all types. All but Array would be covariant; SA would be like RA but with the addition of a Swap method [allowing it to be sorted despite being covariant]. – supercat Feb 07 '13 at 16:12
  • @OlivierJacot-Descombes: It's not possible to implement a type-independent `Sort` method without having some form of writable covariant array. Since such methods are obviously useful, arrays were made covariant so as to permit them. In the era when .net was being designed, neither the usefulness of immutable types, nor had the problems that array variance poses, were apparent as they are today. – supercat Feb 07 '13 at 16:22
  • @supercat: An intuitive way of handling variance is to use call site variance. It allows you to apply co- and contravariance on the same type in a safe way. `public void AddToList(IList list)` or `public void PrintList(IList list)`. – Olivier Jacot-Descombes Feb 07 '13 at 16:31
  • I don't see any way that could work. I `IList` inherited `IDataSink` a type that was generic in `T` one could have a method `public void AddToList(IDataSink list)` in addition to `public void PrintList(IEnumerable list)` but alas `IList` doesn't inherit any contravariant interfaces. – supercat Mar 26 '13 at 20:26
3

The short answer is that in general returning the most specific type available is recommended by the authoritative Framework Design Guidelines. (sorry I don't have a citation on hand, but I remember this clearly since it stuck out in contrast to the Java community guidelines which prefer the opposite).

This makes sense to me. You can always do e.g. IList<int> list = x.ToList(), only the library author needs to be concerned with being able to support the concrete return type.

ToLookup<T> is the unique one in the crowd. But perfectly within the guidelines: it is the most specific type available that the library authors are willing to support (as others have pointed out, the concrete Lookup<T> type appears to be more of an internal type not meant for public use).

Stephen Swensen
  • 22,107
  • 9
  • 81
  • 136
  • 1
    Theres a nice and comprehensive topic about Framework Design Guidelinen on Docs Microsoft, maybe is from the book Stephen mentioned: https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/ – koviroli May 02 '22 at 17:08
2

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();
}
Jani Hyytiäinen
  • 5,293
  • 36
  • 45
1

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.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • Same signature, same return type, no? Maybe the concrete implementation will differ but the return type cannot. – Jodrell Feb 07 '13 at 16:49
  • @Jodrell: The question is whether a function whose present implementation always returns a particular type should return that precise type, or should return a less-specific type. I would posit that a factory may as well return the most specific type that would fit everything a future version might want to return, and that in many cases factories for freely-mutable types are far less likely to want to return widely-varied types than factories from immutable or essentially-immutable types. – supercat Feb 07 '13 at 19:25