I’m using .NET Framework 4.0.
The magic is somewhere within the OrderBy() LINQ method. Here’s a couple of examples for you:
var list = new List<int> { 1, 2, 3, 4, 5, 6};
foreach (var item in list)
{
if (item % 2 == 0)
list.Remove(item);
}
This loop, as expected, throws the "System.InvalidOperationException: Collection was modified; enumeration operation may not execute." exception.
However if I add a call to OrderBy():
foreach (var item in list.OrderBy(v => v))
{
if (item % 2 == 0)
list.Remove(item);
}
the code executes just fine, removing all the even numbers from the list.
At first I assumed that OrderBy() just enumerated the source list and created a sorted copy of it. This would make sense and explain why the loop does not throw an exception: I’m not enumerating the same list as I’m modifying. However in the documentation ("Remarks" section) it is stated that:
This method is implemented by using deferred execution. The immediate return value is an object that stores all the information that is required to perform the action. The query represented by this method is not executed until the object is enumerated either by calling its GetEnumerator method directly or by using foreach in Visual C# or For Each in Visual Basic.
So is it an error in the documentation (maybe accidental copy-pasting of this block?) or did I miss something?
P.S. There’s this question, but the most upvoted answer assumes that OrderBy() just enumerated the list. I’m curious to know whether it’s true or not (a reference to some .NET source
is very welcome). Maybe the copy is indeed not created, but the source list is completely enumerated before I modify it?
>Solution :
I think this is because when the OrderBy starts executing it creates a copy of the list in the Buffer<T> class by calling ToArray, therefore modifying the original list doesn’t throw an exception. Here is a reference to the source code
internal Buffer(IEnumerable<TElement> source)
{
if (source is IIListProvider<TElement> iterator)
{
TElement[] array = iterator.ToArray();
_items = array;
_count = array.Length;
}
else
{
_items = EnumerableHelpers.ToArray(source, out _count);
}
}
Buffer is initialized in the GetEnumerator method:
public IEnumerator<TElement> GetEnumerator()
{
Buffer<TElement> buffer = new Buffer<TElement>(_source);
if (buffer._count > 0)
{
int[] map = SortedMap(buffer);
for (int i = 0; i < buffer._count; i++)
{
yield return buffer._items[map[i]];
}
}
}