If I use:
var strings = new List<string> { "sample" };
foreach (string s in strings)
{
Console.WriteLine(s);
strings.Add(s + "!");
}
the Add
in the foreach
throws an InvalidOperationException (Collection was modified; enumeration operation may not execute), which I consider logical, since we are pulling the rug from under our feet.
However, if I use:
var strings = new List<string> { "sample" };
strings.ForEach(s =>
{
Console.WriteLine(s);
strings.Add(s + "!");
});
it promptly shoots itself in the foot by looping until it throws an OutOfMemoryException.
This comes as a suprise to me, as I always thought that List.ForEach was either just a wrapper for foreach
or for for
.
Does anyone have an explanation for the how and the why of this behavior?
(Inpired by ForEach loop for a Generic List repeated endlessly )
It's because the ForEach
method doesn't use the enumerator, it loops through the items with a for
loop:
public void ForEach(Action<T> action)
{
if (action == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
}
for (int i = 0; i < this._size; i++)
{
action(this._items[i]);
}
}
(code obtained with JustDecompile)
Since the enumerator is not used, it never checks if the list has changed, and the end condition of the for
loop is never reached because _size
is increased at every iteration.
List<T>.ForEach
是通过for
inside实现的,所以它不使用枚举器,它允许修改集合。
Because the ForEach attached to the List class internally uses a for loop that is directly attached to its internal members -- which you can see by downloading the source code for the .NET framework.
http://referencesource.microsoft.com/netframework.aspx
Where as a foreach loop is first and foremost a compiler optimization but also must operate against the collection as an observer -- so if the collection is modified it throws an exception.
We know about this issue, it was an oversight when it was originally written. Unfortunately, we can't change it because it would now prevent this previously working code from running:
var list = new List<string>();
list.Add("Foo");
list.Add("Bar");
list.ForEach((item) =>
{
if(item=="Foo")
list.Remove(item);
});
The usefulness of this method itself is questionable as Eric Lippert pointed out, so we didn't include it for .NET for Metro style apps (ie Windows 8 apps).
David Kean (BCL Team)
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.