4

Should I return IEnumerable<T> from methods and properties only in cases when it is lazy evaluated?

Do you have guys any patterns when you return IEnumerable, ICollection and IList?

Danil
  • 1,813
  • 1
  • 20
  • 22
  • You have your answers at below links : http://stackoverflow.com/questions/1072614/should-i-always-return-ienumerablet-instead-of-ilistt http://stackoverflow.com/questions/3559868/list-ilist-ienumerable-iqueryable-icollection-which-is-most-flexible-return – Ravia Feb 07 '12 at 09:31
  • 2
    @Serge, Ravia - the OP's criterion for lazy evaluation makes it subtly different from the other links. – StuartLC Feb 07 '12 at 09:35
  • 1
    @StuartLC is correct this question shouldn't be closed as a duplicate as that question was not centered around lazy evaluation (deferred execution). There is a however one answer to that question that elaborates a little more about deferred execution (https://stackoverflow.com/a/1072697/5430045). For more information about deferred execution see [Microsoft's Deferred execution and lazy evaluation](https://docs.microsoft.com/en-us/dotnet/standard/linq/deferred-execution-lazy-evaluation) – Jonathan Van Dam May 21 '21 at 19:31

5 Answers5

5

I return IEnumerable whenever the caller will only need to iterate over the collection from start to finish. By using the most minimal possible interface, I ensure that the calling code is not too tightly coupled, and thus make it easier to change the code in the method or property later. If it still returns IEnumerable, nobody else has to care. If it returned List before and the new method internally represents things using, say, a HashSet, suddenly lots of other things have to change as well.

Therefore I always prefer to return collection interfaces rather than concrete collections, except in cases where the calling code really needs to know exactly what type of collection it's dealing with. I consider the operations the caller is going to need to be able to do, and find the most minimal interface which supports them.

Matthew Walton
  • 9,322
  • 3
  • 25
  • 36
  • But returning IEnumerable is a special case - it changes the behaviour of the return to an iterator method - I think that is what the OP is getting at. – Rob Levine Feb 07 '12 at 09:32
  • @Matthew, If you are writing code that will be used by others developers it is not clear how they will use it, Do they need just iterate or they need to know Count etc. – Danil Feb 07 '12 at 10:01
  • Rob, yes, that is something that sometimes causes problems but I tend to prefer the caller to handle it by eagerly enumerating the return if they need to (but then, I'm also a Haskell programmer). Danil, my phrasing was poor perhaps as the author of the method also has the influence of what the caller is allowed to do with it. – Matthew Walton Feb 07 '12 at 11:38
3

Usually if it is a list (not lazy) I return IList. Say, I do it all the time when my method is expected to return the result as a collection. I do it because it clearly states in the method signature that it is a loaded list, not lazy evaluated one, but yet using interface does not expose the concrete type (array, list, read-only list, etc).

I return IEnumerable when I do any kind of transformation on a collection that is passed in (LINQ approach)

At the same time I use IEnumerable as a parameters in my methods just to state that "this function doesn't care, it just need to be able to go through". Of course I ensure that the IEnumerable is enumerated only once within my function.

Alexey Raga
  • 7,145
  • 1
  • 26
  • 34
2

You return IEnumerable<T> if the result is supposed to be read only for the caller of your method.

Daniel Hilgarth
  • 159,901
  • 39
  • 297
  • 411
  • 1
    This answer doesn't tackle the nub of the issue of iterator method vs. normal method and the pros and cons – Rob Levine Feb 07 '12 at 09:29
  • If I return new List then I don't care if it is readonly or not. I mean that I return new List that is not a member of any object. – Danil Feb 07 '12 at 09:33
  • +1 never thought of this before. So you wrap your collection of whatever type with another enumerator to guarantee the collection is truly read-only (given their elements are immutable) rather than just returning it, don't you? – Matthias Meid Feb 07 '12 at 09:35
  • @Mudu: No. I actually return a `List` most of the time. So if someone wants to cast it to a list, he can. It's all about communication: With returning an `IEnumerable` I communicate that the result should only be iterated over but is not meant to be changed. This is especially true for properties. – Daniel Hilgarth Feb 07 '12 at 09:47
  • @RobLevine: I don't follow. Care to expand your comment a bit? What do you mean when you refer to an `iterator method`? – Daniel Hilgarth Feb 07 '12 at 09:48
  • @Daniel - Your point is a good one, but there is also another consideration. By making the return type an IEnumerable you are turning the method into an iterator method. Its behaviour is now totally different with regard to when it is called. It isn't called when "it looks like it is" in your code, it is only called when the enumerator is evaluated - giving a lazy behaviour. This has advantages and disadvantages - I thought maybe you could expand on those a little to make the answer more complete. – Rob Levine Feb 07 '12 at 09:51
  • @Daniel - http://msdn.microsoft.com/en-us/library/dscyy5s0(v=vs.80).aspx – Rob Levine Feb 07 '12 at 09:52
  • @RobLevine: That's simply not correct, sorry. Maybe you should read up on that topic. You are mixing things here. Returning `IEnumerable` only loosely relates to the `yield` keyword. – Daniel Hilgarth Feb 07 '12 at 09:54
  • `var result = Method1(); // ` – Daniel Hilgarth Feb 07 '12 at 09:57
  • Indeed - I should have been specific - it only changes the behaviour in the presence of the yield return keyword. But surely that is implicit in the OPs question - he is asking about normal vs. lazy evaluation. That is the nub of his question isn't it? I read that question to say "talk to me about normal methods vs. lazy evaluated IEnumerator ones". – Rob Levine Feb 07 '12 at 10:03
  • @RobLevine: I'm reading "what implementation elements do your method signature design decisions base on?" – Matthias Meid Feb 07 '12 at 10:08
  • @DanielHilgarth: All right, so using `IEnumerable` is rather sort of hint to the consumer than a strong guarantee? (The consumer might - though I admit this is very abstract in most environments - cast the return value back to `IList`, which is not possible if you turn it into a real iterator method.) – Matthias Meid Feb 07 '12 at 10:11
  • another quick point - in the absence of the yield keyword (ie when this is not an iterator method), then it only partially protects the return values as being read only. It can still be cast to the underlying type - and then mutated. The reason I short circuited with the assumption you were talking about an iterator method originally - is because the protection you propose only truly exists with an iterator method. (edit: just posted this to see that Mudu has made the same point!) – Rob Levine Feb 07 '12 at 10:12
  • @RobLevine: As I already replied to Mudu earlier, this is about communication of intent. In a full trust environment, you can't "protect" or "enforce" much anyway. – Daniel Hilgarth Feb 07 '12 at 10:13
  • On the point of intent - You can't tell an iterator method (one that uses yield) from one that does not from the method signature, even though its behaviour is radically different. Is there not possibly an "intent" issue around this too. How do you express the intent that "this is an iterator method and will behave differently" from its signature? Is there a case for not using IEnumerable as it may mistakenly suggest the intent that this is an iterator method? Or should all consumers code in case it is (e.g. some iterator methods may not be designed for multiple "passes" of the enumerator)? – Rob Levine Feb 07 '12 at 10:26
2

IEnumerable<T> should be returned in these cases:

  1. Result doesn't matter if it's an array, list or any kind of collection, because consumer needs to iterate it only.

  2. Result is typed using an implementation of IEnumerable<T>, but public API doesn't force consumers to work with API-specific types so consumers keeps neutral using POCO.

Specific implementations of IEnumerable<T> like ICollection<T>, IList<T>, List<T> and so on, should be returned if result is expected to be a collection. That's consumers should be able to access enumerable members with an indexer, or they should modify the collection itself (adding, updating, removing, searching... items in the collection).

Choosing the right concretion in your typing for properties and return types is more about a good case analysis and just type what your consumers need.

For example, conceptually talking, some property should return a list, but a consumer shouldn't modify it, so, this property should return a ReadOnlyCollection<T> but you'd type it as IList<T>.

As I said above, good case analysis and keep it simple.

Matías Fidemraizer
  • 59,064
  • 16
  • 107
  • 181
1

As a rule of thumb:

I return ICollection if I am sure I always know the number of results I return. If the caller just enumerates, they can easily treat is as an IEnumerable anyway. If the order of elements is anything else than random, I use IList. I use IEnumerable in other cases. With different implementations for one interface, I use the "loosest" interface rather than recopying results in order to meet a specific collection interface.

Matthias Meid
  • 12,080
  • 6
  • 41
  • 73