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?
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.
IEnumerable is read-only and List is not. IEnumerable types have a method to get the next item in the collection.
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.
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 .
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With