Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

List<T> vs IEnumerable<T> in foreach

Tags:

c#

foreach

linq

I was trying to get some Lists sorted using OrderBy within a foreach loop, but for some reason they weren't maintaining their sort order outside of the loop. Here's some simplified code and comments to highlight what was happening:

public class Parent
{
    // Other properties...
    public IList<Child> Children { get; set; }
}

public IEnumerable<Parent> DoStuff()
{
    var result = DoOtherStuff() // Returns IEnumerable<Parent>
        .OrderByDescending(SomePredicate) 
        .ThenBy(AnotherPredicate); // This sorting works as expected in the return value.

    foreach (Parent parent in result)
    {
        parent.Children = parent.Children.OrderBy(YetAnotherPredicate).ToList();
        // When I look at parent.Children here in the debugger, it's sorted properly.
    }

    return result;
    // When I look at the return value, the Children are not sorted.
}

However, when I instead assign result like this:

var result = DoOtherStuff()
    .OrderByDescending(SomePredicate)
    .ThenBy(AnotherPredicate)
    .ToList(); // <-- Added ToList here

then the return value has the Children sorted properly in each of the Parents.

What is the behavior of List<T> vs an IEnumerable<T> in a foreach loop?

There seems to be some sort of difference since turning result into a List fixed the problems with sorting in the foreach loop. It feels like the first code snippet creates an iterator that makes a copy of each element when you do the iteration with foreach (and thus my changes get applied to the copy but not the original object in result), while using ToList() made the enumerator give a pointer instead.

What's going on here?

like image 961
wlyles Avatar asked Aug 25 '15 23:08

wlyles


People also ask

What's the difference between IEnumerable T and List t?

IEnumerable is a deferred execution while List is an immediate execution. IEnumerable will not execute the query until you enumerate over the data, whereas List will execute the query as soon as it's called. Deferred execution makes IEnumerable faster because it only gets the data when needed.

Is List IEnumerable in C#?

IEnumerable is read-only and List is not. IEnumerable types have a method to get the next item in the collection.

Why should I use IEnumerable?

IEnumerable is best to query data from in-memory collections like List, Array etc. IEnumerable doesn't support add or remove items from the list. Using IEnumerable we can find out the no of elements in the collection after iterating the collection. IEnumerable supports deferred execution.

How does an IEnumerator differ from an IEnumerable?

An IEnumerator is a thing that can enumerate: it has the Current property and the MoveNext and Reset methods (which in . NET code you probably won't call explicitly, though you could). An IEnumerable is a thing that can be enumerated...which simply means that it has a GetEnumerator method that returns an IEnumerator .


2 Answers

The difference is that one is an expression that can procuce a set of Parent objects, and the other is a list of Parent objects.

Each time that you use the expression, it will use the original result from DoOtherStuff and then sort them. In your case it means that it will create a new set of Parent objects (as they obviously don't retain the children from the previous use).

This means that when you loop through the objects and sort the children, those objects will be thrown away. When you use the expression again to return the result, it will create a new set of objects where the children naturally is in the original order.

like image 113
Guffa Avatar answered Oct 07 '22 20:10

Guffa


Sample code of what likely happens to add to Guffa's answer:

class Parent { public List<string> Children; }

Enumerable of "Parent", will create new "Parent" objects every time it is iterated:

var result = Enumerable.Range(0, 10)
      .Select(_ => new Parent { Children = new List<sting>{"b", "a"});

Now first iteration with foreach there will be 10 "Parent" objects created (one for each iteration of the loop) and promptly discarded at the end of each iteration:

foreach (Parent parent in result)
{
    // sorts children of just created parent object
    parent.Children = parent.Children.OrderBy(YetAnotherPredicate).ToList();

    // parent is no longer referenced by anything - discarded and eligible for GC
}

When you look at result again it will be re-iterated and new set of "Parent" objects created every time you look at it, hence "Children" are not sorted.

Note that depending on how DoOtherStuff() // Returns IEnumerable<Parent> is implemented result could be different. I.e. DoOtherStuff() can return collection of existing items from some cached collection:

 List<Parent> allMyParents = ...; 

 IEnumerable<Parent> DoOtherStuff()
 {
      return allMyParents.Take(7);
 }

Now every iteration of result will give you new collection, but each item in the collection will just be an item from allMyParents list - so modification "Children" property would change the instances in allMyParents and change would stick.

like image 33
Alexei Levenkov Avatar answered Oct 07 '22 18:10

Alexei Levenkov