views:

458

answers:

7

I keep hearing that in .net 3.5 you should use IEnumerable over a List, but I can’t find any reference materials or articles that explain why it’s so much more proficient. Does anyone know of any content that explains this?

The purpose of asking this question is to get a better understanding of what IEnumerable is doing under the hood. If you can provide me with any links I will do the research and post an answer.

+4  A: 

IEnumerable<T> is not more efficient than a List<T> as a List<T> is an IEnumerable<T>.

The IEnumerable<T> interface is simply .NET's way of using the iterator pattern, nothing more.

This interface can be implemented on many types (List<T> included) to allow those types to to return iterators (i.e. instances of IEnumerator<T>) so that the caller can iterate a sequence of items.

Andrew Hare
(-1) A minimalist IEnumerable<t> would be smaller and faster than IList<t>. Not by much, but List<t> has overhead because it is more complicated.
I disagree. As pointed out by other responses it's possible to return an `IEnumerable<T>` implementation without creating an instance of anything, and which supports deferred execution for expensive operations.
ph0enix
To clarify: I disagree with the answer, not with the previous comment.
ph0enix
@devinb? Do you think it's worth a downvote? I thought Andrew was just using the classic "is a" technique to describe `List<T>` as an implementor of `IEnumerable<T>`. Or am I misunderstanding your comment?
Greg D
@ph0enix: I believe that you are referring to iterator blocks which _does_ involve creating and instance of a type. That type is compiler generated. Yes, you can also use deferred execution as well but none of these things have anything to do with `IEnumerable<T>` other than they use it as an implementation detail.
Andrew Hare
@Greg: I feel that Andrew's answer does not answer the question in a useful way. The question is about efficiency and performance considerations, and Andrew's answer, although factually correct, glosses over those parts.
@devinb: the question doesn't say anything about performance. "proficient" could refer to performance or correctness. Performance is likely one concern, but it's not the only concern.
Joel Coehoorn
+19  A: 

IEnumerable<T> is an interface that is implemented by List<T>. I suspect the reason you're hearing that IEnumerable<T> should be used is because it's a less constrictive interface requirement.

For example, consider the following method signature:

void Output(List<Foo> foos) 
{ 
    foreach(var foo in foos) { /* do something */ }
}

This method requires that it be passed a concrete implementation of a List. But it's just doing something in-order. It doesn't really need random access or any of the other things that a List<T> or even an IList<T> give it. Instead, the method should accept an IEnumerable<T>:

void Output(IEnumerable<Foo> foos) 
{ 
    foreach(var foo in foos) { /* do something */ }
}

Now we're using the most general (least specific) interface that supports the operations that we need. This is a fundamental aspect of OO-design. We've decreased coupling by requiring only what we need, and not a whole lot else besides. We've also created a more flexible method because the foos parameter might be a Queue<T>, a List<T>, anything that implements IEnumerable<T>. We aren't forcing the caller to convert their data structure to a List unnecessarily.

So it isn't that IEnumerable<T> is more efficient than list in a "performance" or "runtime" aspect. It's that IEnumerable<T> is a more efficient design construct because it's a more specific indication of what your design requires. (Though this can lead to runtime gains in specific cases.)

Greg D
This is all true, but you've missed the important point regarding the efficiency of deferred execution for methods that return `IEnumerable<T>`.
ph0enix
Though not all methods that return `IEnumerable<T>` utilize deferred execution (presumably, this is why the original answer mentions that "this can lead to runtime gains in specific cases").
Jeff Sternal
You can implement deferred execution in your own implementation of any interface, including `IList<T>`, or in your own extension of any base class.
Justice
Jeff is correct, that's why I put that there. In the context of the question, I consider any runtime gains to be icing on the cake. IE, a reward for following good design principles. :)
Greg D
A: 

These are two different beasts and you cannot really compare them. For instance, in var q = from x in ... the q is IEnumerable, but under the hood it executes a very expensive database call.

An IEnumerable is just an interface for an Iterator design pattern, whereas List/IList is a data container.

Anton Gogolev
+17  A: 
Joel Coehoorn
Well put! I didn't quite have the energy to put as much detail into my answer, so I'm glad you did! :)
ph0enix
+1 for your answer!! Unfortunately, I can't mark two answers.
Zaffiro
+1  A: 

One reason that it is recommended to have methods return IEnumerable<T> is that it is less specific than List<T>. This means that you can later change the internals of your method to use something that may be more efficient for the needs of it, and as long as it is an IEnumerable<T> you don't need to touch the contract of your method.

Fredrik Mörk
+2  A: 

It isn't a question of efficiency (although that may be true) but of flexibility.

Your code becomes more re-usable if it can consume an IEnumerable instead of a List. AS to efficient consider this code:-

 function IEnumerable<int> GetDigits()
 {

    for(int i = 0; i < 10; i++)
       yield return i
 }

 function int Sum(List<int> numbers)
 {
    int result = 0; 
    foreach(int i in numbers)
      result += i;

    return i;
 }

Q: How do I take the set of numbers generated by GetDigits and get Sum to add them up?
A: I need to load the set of numbers from GetDigits into a List object and pass that to the Sum function. This uses memory as all the digits need to be loaded into memory first before they can be summed. However changing the signature of Sum to:-

 function int Sum(IEnumerable<int> numbers)

Means I can do this:-

 int sumOfDigits = Sum(GetDigits());

No list is loaded into memory I only need storage for the current digit and the accumulator variable in sum.

AnthonyWJones
A: 
ph0enix
You aren't creating a new `List<int>` but you _are_ allowing the compiler to create a state machine on your behalf to make this all work.
Andrew Hare